DynamoDB
DynamoDBの概要 qiita.com
RDSとの比較 dev.classmethod.jp
使い所
RDSよりも早いレスポンスが要求される場合 スケールアウトすることにより、リクエスト数の増加に対応もできる。RDSだとスケールアップでの対応になり限界がある。
サービスを一時的に止めたくない場合 RDSの場合はフェイルオーバーやメンテナンス時点で止まる可能性がある。
事前にどのように検索するかが把握できている場合 検索できる属性が決まっているので。 GSIを追加することで回避はできるがコストがかかる。ただGSIは結果整合性のみサポートなので、リアルタイム反映が必要な場合は要件に合わない。
一時的に古いデータが返ってくるのが許容される場合 強い整合性を選択すれば回避はできるがスループットに影響がありそう
form外にあるbuttonからpostする方法
課題
- form内にsubmitするbuttonを設置することができない
対処法
- buttonのform属性に以下のように指定する
<form action="{{ route('brands.pages.edit', ['id' => $brand->id]) }}" method="POST" id="page-edit-all-tabs-form" //idの指定をする enctype="multipart/form-data"> {{ csrf_field() }} <div class="tab-content"> @include('admin.store.modal.facebook') </div> </form> <div class="footer-page"> <a href="{{ route('brands.pages') }}"> <button type="button" class="btn btn-light btn-light-custom">キャンセル</button> </a> @if ($facebook['has_token']) <button type="submit" form="page-edit-all-tabs-form" // formのidを同じものを指定する class="btn btn-primary margin-left-10px color-button search-btn border-none update-btn">保存する </button> @endif </div>
複数のワーカで処理する際の最後の処理をredisで制御する方法
課題
- ワーカを利用し分散処理した際し、最後の処理が終わったタイミングでメールを送信したい場合
対処方法
<?php public function handle(MediaService $mediaService) { Log::info(Utils::getLogStatusStrings() . "Create Media Job Start."); //既にキューが実行済みか確認 $isExecuted = Cache::tags($this->jobId)->has($this->storeId); if ($isExecuted) { Log::info('isExecuted'); return; } Cache::tags($this->jobId)->put($this->storeId, '', 60 * 24); try { // } catch(Throwable $e) { // } catch(LocationIdNullException $e) { // } Cache::tags($this->jobId)->put($this->storeId, $message, 60 * 24); if ($this->isLastQueue($this->jobId)) { $this->notifyResult($this->jobId); Cache::forget($this->jobId); } Log::info(Utils::getLogStatusStrings() . "Create Media Job End."); } //最後のキューか確認 private function isLastQueue(string $jobId): bool { $currentNum = (int) Cache::increment($jobId); return $this->total === $currentNum; }
- 別解としてFIFOのSQSを使うという方法があるが、その方法だと処理スピードが遅くなるので今回は除いている。
【Laravel】バッチ処理の結果をDBに保存する方法
課題
- 各バッチ処理が成否を素早く確認したい
- またサーバが落ちてしまった場合に何が処理できていなかったのかの確認も素早くしたい
- ログ出力だと確認に時間がかかる。
対処方法
- バッチ処理結果を保存するDBを作成。今回は
JobsLog
とする - 以下のようなクラスの作成
<?php class JobsLog extends Eloquent { protected $table = 'jobs_log'; protected $casts = [ 'status' => 'int' ]; const STATUS_PROCESS = 1; const STATUS_SUCCESS = 2; const STATUS_FAILED = 0; const STATUS_NOT_RUN = 3; const UNSENT_MAIL = 0; const SENT_MAIL = 1; const WEEK_MAP = [ 0 => 'SU', 1 => 'MO', 2 => 'TU', 3 => 'WE', 4 => 'TH', 5 => 'FR', 6 => 'SA', ]; const TYPE_DAILY = 1; const TYPE_WEEKLY = 2; const TYPE_MONTHLY = 3; const TYPE_MINUTELY = 4; protected $dates = [ 'start_time', 'end_time' ]; protected $fillable = [ 'job_name', 'status', 'start_time', 'end_time', 'message', 'send_mail', 'created_at', ]; public static function createJobLog(String $jobName) { JobsLog::firstOrCreate( [ 'job_name' => $jobName, 'created_at' => Carbon::now()->format('Y-m-d'), ], [ 'status' => JobsLog::STATUS_PROCESS, 'start_time' => Carbon::now(), ] ); } public static function updateJobLog(String $jobName) { JobsLog::where([ 'job_name' => $jobName, 'created_at' => Carbon::now()->format('Y-m-d'), 'status' => JobsLog::STATUS_PROCESS, ])->update([ 'status' => JobsLog::STATUS_SUCCESS, 'end_time' => Carbon::now(), ]); } public static function jobFail(String $jobName, $message) { JobsLog::where([ 'job_name' => $jobName, 'created_at' => Carbon::now()->format('Y-m-d'), 'status' => JobsLog::STATUS_PROCESS, ])->update([ 'status' => JobsLog::STATUS_FAILED, 'end_time' => Carbon::now(), 'message' => $message, ]); } public static function createLogJobNotRun(String $jobName) { JobsLog::firstOrCreate( [ 'job_name' => $jobName, 'created_at' => Carbon::now()->format('Y-m-d'), ], [ 'status' => JobsLog::STATUS_NOT_RUN, 'send_mail' => JobsLog::SENT_MAIL, ] ); } //実行するべきバッチ処理のリストを取得する。 public static function listJobRun() { $listJob = []; $media = (env('GET_MEDIAS')) ? true : false; $getLocalPost = (env('GET_LOCAL_POSTS')) ? true : false; if ($media) { $key = (new MediasPreserveScheduler())->getCmd(); $listJob[$key] = array( 'status' => $media, 'type' => JobsLog::TYPE_DAILY, 'time' => config('schedule.get_medias'), 'dayOfWeek' => '', 'dayOfMonth' => '' ); } // local post if ($getLocalPost) { $key = (new LocalPostsPreserveScheduler())->getCmd(); $listJob[$key] = array( 'status' => $getLocalPost, 'type' => JobsLog::TYPE_DAILY, 'time' => config('schedule.local_posts_get'), 'dayOfWeek' => '', 'dayOfMonth' => '' ); } return $listJob; } }
- バッチ処理がサーバダウンで処理されていなかった場合にslackに通知するクラス
<?php class ScanJobSchedule extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'jobs:check_log'; /** * The console command description. * * @var string */ protected $description = 'Check the to-do list and send a failure message'; /** * Create a new command instance. * * @return void */ public function __construct() { parent::__construct(); } /** * Execute the console command. * * @return mixed */ public function handle() { try { $runJobs = JobsLog::listJobRun(); $jobs = JobsLog::whereDate('created_at', '=', date('Y-m-d')); $this->failJobs($jobs); if (!empty($runJobs)) { $this->ranJobs($runJobs, $jobs); } Log::info('jobs:check_log: success'); } catch (\Throwable $e) { echo $e->getMessage(); Log::error($e->getMessage()); } } public function failJobs($jobs) { $jobs = (clone $jobs)->where('status', JobsLog::STATUS_FAILED)->get(); foreach ($jobs as $job) { if ($job->send_mail == JobsLog::UNSENT_MAIL) { $msg = 'Command: '. $job->job_name .' ('. env('APP_URL') . ') 》not run ' . "\n" . $job->message; Notification::route('slack', env('SLACK_WEBHOOK_URL')) ->notify(new UpdateStoreByUsingCsvFileNotification($msg)); Log::error($msg); $job->update([ 'send_mail' => JobsLog::SENT_MAIL, ]); } } } public function ranJobs($runJobs, $jobs) { foreach ($runJobs as $key => $runJob) { // run daily if ($runJob['type'] == JobsLog::TYPE_DAILY || $runJob['type'] == JobsLog::TYPE_MINUTELY) { $timeRunSub2 = Carbon::parse($runJob['time'])->addMinutes(2); $timeCurrent = Carbon::now(); if ($timeRunSub2->greaterThan($timeCurrent)) { continue; } if ((clone $jobs)->where('job_name', $key)->exists()) { continue; } $msg = 'Command: '. $key .' ('. env('APP_URL') . ') 》not run'; Notification::route('slack', env('SLACK_WEBHOOK_URL')) ->notify(new UpdateStoreByUsingCsvFileNotification($msg)); Log::error($msg); JobsLog::createLogJobNotRun($key); } // run weekly if ($runJob['type'] == JobsLog::TYPE_WEEKLY) { $dayOfTheWeek = Carbon::now()->dayOfWeek; if (JobsLog::WEEK_MAP[$dayOfTheWeek] != $runJob['dayOfWeek']) { continue; } $timeRunSub2 = Carbon::parse($runJob['time'])->addMinutes(2); $timeCurrent = Carbon::now(); if ($timeRunSub2->greaterThan($timeCurrent)) { continue; } if ((clone $jobs)->where('job_name', $key)->exists()) { continue; } $msg = 'Command: '. $key .' ('. env('APP_URL') . ') 》not run'; Notification::route('slack', env('SLACK_WEBHOOK_URL')) ->notify(new UpdateStoreByUsingCsvFileNotification($msg)); Log::error($msg); JobsLog::createLogJobNotRun($key); } } } }
Kernel.php
<?php if (env('GET_MEDIAS')) { $cmd = (new MediasPreserveScheduler())->getCmd(); $schedule->command('medias:get')->dailyAt(config('schedule.get_medias')) // ->everyMinute() ->sendOutputTo(storage_path('logs/' . strval(date('Y-m-d')) . '_medias' . '.log')) ->before(function () use ($cmd) { JobsLog::createJobLog($cmd); //スタート時点のログを追加 }) ->after(function () use ($cmd) { JobsLog::updateJobLog($cmd);//終了のログを追加 })->runInBackground(); } if (env('IKKATSU_JOB_CHECK')) { $schedule->command('jobs:check_log') ->everyFiveMinutes(); }
MediasPreserveScheduler.php
の処理
<?php class MediasPreserveScheduler extends Command { /** * The name and signature of the console command. * @var string */ protected $signature = 'medias:get'; /** * The console command description. * @var string */ protected $description = 'get medias information per month'; /** * Create a new command instance. * @return void */ public function __construct() { parent::__construct(); } /** * Execute the console command. * @return mixed * @throws GuzzleException */ public function handle() { try { //処理 } catch (Throwable $e) { report($e); JobsLog::jobFail($this->getCmd(), $e->getMessage()); //処理が失敗した場合のログを追加 } } public function getCmd() { return $this->signature; } }
【CSS】クラスの部分一致
- html
<ul class="list_nav_header"> <li><a href="#">ABOUT.</a></li> <li><a href="#">FEATURE.</a></li> <li><a href="#">PROFILE.</a></li> <li><a class="btn_ico_mail" href="#">CONTACT.</a></li> </ul>
[class*="btn_ico"] { display: block; height: 36px; display: flex; align-items: center; padding: 0 30px 0 60px; font: 400 18px/1em 'futura', 'noto sans japanese', sans-serif; background: #00CCFF; position: relative; }
クラス名の btn_icoと含まれてる場合にスタイルを当てる。
[class*="btn_ico"][class*="mail"]:before { background: url(../img/ico/ico_mail.svg); }
クラス名にbtn_icoとmailが含まれてる場合にスタイルを当てる。
【CSS】文頭にアイコンを表示
方法
①擬似要素の利用
- html
<ul class="list_nav_header"> <li><a href="#">ABOUT.</a></li> <li><a href="#">FEATURE.</a></li> <li><a href="#">PROFILE.</a></li> <li><a class="btn_ico_mail" href="#">CONTACT.</a></li> </ul>
[class*="btn_ico"] { display: block; height: 36px; display: flex; align-items: center; padding: 0 30px 0 60px; font: 400 18px/1em 'futura', 'noto sans japanese', sans-serif; background: #00CCFF; position: relative; } [class*="btn_ico"]:before { content: ""; display: block; width: 40px; /*svgの大きさに合わせる*/ height: 40px; /*svgの大きさに合わせる*/ background-position: center center; background-repeat: no-repeat; background-size: contain; position: absolute; /*元のテキストはposition: relativeの必要あり*/ top: 50%; left: 15px; transform: translateY(-50%); } [class*="btn_ico"][class*="mail"]:before { background: url(../img/ico/ico_mail.svg); }