OmniAICreator/Qiita-1.07M huggingface dataset:
Unable to negotiate with : no matching host key type found. Their offer: ssh-rsa,ssh-dss


# 事象 : 踏み台の先にあるEC2へssh接続しようとしたら怒られた
- 環境
    - OpenSSH_8.8p1, OpenSSL 1.1.1m  14 Dec 2021
    - 踏み台OS : Amazon Linux release 2 (Karoo)
    - 接続先OS : CentOS release 6.7 (Final)

```bash
$ ssh old-host
Enter passphrase for key '/path/to/fumidai.pem': 
Unable to negotiate with UNKNOWN port 65535: no matching host key type found. Their offer: ssh-rsa,ssh-dss
```

```config:~/.ssh/config
# 踏み台
Host fumidai
    HostName xx.1xx.1xx.1xx
    User ponsuke
    IdentityFile /path/to/fumidai.pem

# 接続先
Host old-host
    HostName 10.0.x.xx
    User tarou
    IdentityFile /path/to/id_rsa
    ProxyCommand ssh fumidai -W %h:%p
```

## 原因 : OpenSSH8.8からSHA-1のRSA鍵は無効になったから

> This release disables RSA signatures using the SHA-1 hash algorithm by default.
> https://www.openssh.com/txt/release-8.8

(ざっくり訳)本リリースでは、SHA-1ハッシュアルゴリズムを使用したRSA署名をデフォルトで無効化します。

今回は踏み台の鍵でパスフレーズを聞かれた後エラーになったので、たぶん接続先のホストに使っている鍵が古いようだ。
秘密鍵でアルゴリズムを確認できなかったので、ホストへ出かけて公開鍵でアルゴリズムを確認してみた。

```bash
# 踏み台はSHA-2だった
$ ssh-keygen -l -f authorized_keys
2048 SHA256:... (RSA)
# 接続先はSHA-1...
$ ssh-keygen -l -f .ssh/authorized_keys 
3072 xx:...:xx .ssh/authorized_keys (RSA)
```
同じようなことがOpenSSH7.0ではDSA鍵であったらしい。

> OpenSSH 7.0 and greater similarly disable the ssh-dss (DSA) fumidai key algorithm.
> [OpenSSH: Legacy Options](http://www.openssh.com/legacy.html)

(ざっくり訳)OpenSSH7.0以降では、ssh-dss (DSA) 公開鍵アルゴリズムが無効にされています。

### 対応 : .ssh/configに設定を追加する
- 参考 : [OpenSSHの暗号化周りの設定について - Qiita](https://qiita.com/aqmr-kino/items/8c3306ea8022b0d5cbe4#%E5%85%AC%E9%96%8B%E9%8D%B5%E9%96%A2%E9%80%A3pubkeyacceptedkeytypeshostkeyalgorithms)

ホストの公開鍵の種類を古い鍵も使えるように追加をします。

```
Host old-host
    # ...省略...
    HostkeyAlgorithms +ssh-rsa
```
.ssh/configの設定を変えたら`ssh-keygen -R`で、古い情報をknown_hostsから消します。
これを忘れると[WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!](https://qiita.com/ponsuke0531/items/9497895fe9217e5b40b3)というエラーになるので注意してください。


```bash
# 古い情報をknown_hostsから消してあげる
$ ssh-keygen -R 10.0.x.xx
# Host 10.0.x.xx found: line 34
/c/Users/ponsuke/.ssh/known_hosts updated.
Original contents retained as /c/Users/ponsuke/.ssh/known_hosts.old

# もう一度接続する
$ ssh old-host
Enter passphrase for key '/path/to/fumidai.pem': 
The authenticity of host '10.0.x.xx (<no hostip for proxy command>)' can't be established.
RSA key fingerprint is SHA256:xxxxxx.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.0.x.xx' (RSA) to the list of known hosts.
Enter passphrase for key '/path/to/id_rsa': 
sign_and_send_pubkey: no mutual signature supported
ponsuke@10.0.x.xx's password:
Last login: Thu Feb 17 14:32:13 2022 from xx.1xx.1xx.1xx
[ponsuke@ip-10-0-x-xx ~]$
```
### 他の対応
やっていないけど将来のためにメモ、いや、早く鍵交換しなきゃ。

1. 新しいアルゴリズムで鍵を作り直す
    1. 接続先がEC2だと使えないものあるので注意です
        1. [Amazon EC2 が使用するキーは、 ED25519もしくは2048-bit SSH-2 RSA キーです。](https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/ec2-key-pairs.html)
[初心者log]カスタムフックについて


#本記事の目的
Reactを勉強していく中でpropsのバケツリレーばかりしていたので、その解決方法としてカスタムフックについて勉強まとめてみます。

##カスタムフックとは
カスタムフック自体はただの関数なのですが、以下の特徴があります。

- hooksの各機能が使用できる。
- コンポーネントからロジックを分離することができる。
- 使い回し、テストの簡略化、見通しの改善ができる。

暗黙のルールとして命名はuse~の形にするのが推奨されています。

####カスタムフックを使わない場合

```App.js
import React, { useState, useEffect } from "react"
import axios from "axios"
import { getUser } from "../lib/api/user"

const App: React.FC = () => {
  const [loading, setLoading] = useState(false);
  const [user, setUser] = useState();


  //ユーザー情報のAPIを叩く記述
  const handleGetUser = async () => {
    try {
      const res = await getUser(); //ユーザー情報取得の関数（詳細は省略）
      console.log(res);

      if (res?.status === 200) {
        setUsers(res?.data)
      } else {
        console.log("No user");
      }
    } catch (err) {
      console.log(err);
    }

    setLoading(false);
  };

  useEffect(() => {
    handleGetUser()
  },[])

  return (
   <>
    {!loading : (
       return (
        <>
          <h1>{user.name}さん</h1>
          <p>{user.age}歳</p>
          <p>{user.profile}</p>
        </>
      )
    ) : (
    <></>  
    )
   </>
  )
}

```

####カスタムフックを使う場合



```hooks/useUser.js
import { useState } from "react"
import axios from "axios"
import { getUser } from "../../lib/api/user"

export const useUser = () => {
  const [loading, setLoading] = useState(false);
  const [user, setUser] = useState();

  const handleGetUser = async () => {
     try {
      const res = await getUser(); //ユーザー情報取得の関数（詳細は省略）
      console.log(res);

      if (res?.status === 200) {
        setUsers(res?.data)
      } else {
        console.log("No user");
      }
    } catch (err) {
      console.log(err);
    }

    setLoading(false);
  };

  useEffect(() => {
    handleGetUser()
  },[])
  
  return { handleGetUser, user, loading }
}


```



```App.js
import React, { useEffect } from "react"
import axios from "axios"
import { useUser } from "./hooks/useUser"

const App: React.FC = () => {
  const { handleGetUser, user, loading } = useUser();

  useEffect(() => {
    handleGetUser()
  },[])

  return (
   <>
    {!loading : (
       return (
        <>
          <h1>{user.name}さん</h1>
          <p>{user.age}歳</p>
          <p>{user.profile}</p>
        </>
      )
    ) : (
    <></>  
    )
   </>
  )
}
```

##まとめ
カスタムフックを使うことでファイル内がかなりスッキリしました。
また、カスタムフックにまとめることで関数を別の機会にも使えるようになりました。
WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!


# 事象 : .ssh/configに「HostkeyAlgorithms」を追記してからssh接続しようとしたら怒られた
ホストの公開鍵の種類を「HostkeyAlgorithms」で追加して、ssh接続しようとしたら・・・

```bash
$ ssh old-host
Enter passphrase for key '/path/to/fumidai.pem': 
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the DSA key sent by the remote host is
SHA256:....
Please contact your system administrator.
Add correct host key in /c/Users/ponsuke/.ssh/known_hosts to get rid of this message.
Offending RSA key in /c/Users/ponsuke/.ssh/known_hosts:27
Host key for 10.0.x.xx has changed and you have requested strict checking.
Host key verification failed.
```

# 原因 : known_hostsにある情報と接続しようとしている情報が違うから

メッセージをざっくり訳すと・・・

「誰かが何か悪さをしている可能性があります。今まさに盗聴されているかもしれない（中間者攻撃）!また、ホストの鍵が変更されただけということもありえます。.....」

何か心配されている感じ。

## 対応 : 古い情報をknown_hostsから消してあげる
```bash
$ ssh-keygen -R 10.0.x.xx
# Host 10.0.x.xx found: line 27
/c/Users/ponsuke/.ssh/known_hosts updated.
Original contents retained as /c/Users/ponsuke/.ssh/known_hosts.old

# ssh接続時に、再度known_hostsが更新される
$ ssh old-host
Enter passphrase for key '/path/to/fumidai.pem': 
The authenticity of host '10.0.x.xx (<no hostip for proxy command>)' can't be established.
DSA key fingerprint is SHA256:...
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.0.x.xx' (DSA) to the list of known hosts.
Enter passphrase for key '/path/to/id_rsa': 
sign_and_send_pubkey: no mutual signature supported
```
leafletを使って待ち合わせ場所を指定する

## はじめに

Leafletとは、Web上の地図サービスが使えるJavaScriptライブラリです。
利用できる地図は、GoogleマップやYahoo地図といった有名どころだけではなく、国土地理院といった学術的にしか利用されなさそうなお堅い地図まで幅広く利用できます。人に待ち合わせ場所を知らせる場合、色々な方法で伝えることができます。自分がよく使う方法は次のやり方です。
①Googleマップで特定の位置を示したキャプチャを取り、ビットマップに張り付ける
②ビットマップに目的の場所をペンなどで印をつける
③ビットマップをPDFに変換する
このやり方でも悪くはないでしょう。しかし、JavaScriptでLeafletを使えば、手軽に特定の場所を示すことができるようになります。しかも、緯度・経度を変更するだけで、使いまわしができるようになるといったメリットもあります。

## サンプルソースの解説

サンプルコードでは、JR米原駅を待ち合わせ場所として指定しました。

・divで地図を表示するエリアを作ります。styleをposition:absoluteにすると、ブラウザの画面全体に地図が表示できます。
・Lealetが使えるようにするために、headでLeafletのJavaScriptライブラリと、CSSのCDNのリンク先URLを指定します。
・bodyがブラウザにロードされたタイミングでini関数を実行して、地図が初期表示されるようにします。
・mpoint変数に、待ち合わせ場所の緯度、経度を配列で設定します。
・L.mapでLeafletのライブラリが使えるようにします。
・zoomControlをfalseにすることで、拡大縮小ボタンが非表示にできます。しかし、マウス操作では拡大縮小ができるようになっています。
・setViewの第2引数を16にしています。これは、地図の拡大縮小の範囲を表しています。値を小さくすれば縮小し、値を大きくすれば拡大します。待ち合わせ場所を示すには、16が一番妥当な値だと判断しています。
・tileLayerで引用する地図の種類を指定しています。サンプルでは、OpenStreetMapの地図を引用しています。OpenStreetMapとはオープンライセンスで運用されている地図のサイトです。
・attributionで著作権情報を右下に表示することができます。
・maxZoomで拡大可能な最大値を指定します。拡大率を大きくし過ぎると見栄えが悪くなります。18が妥当な値と判断して設定しました。
・L.markerで待ち合わせ場所をマーキングできます。マーキングした場所にカーソルを当てると、titleの内容が表示されます。

## コード

```javascript
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>待ち合わせ場所</title>

    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.4/dist/leaflet.css"
            integrity="sha512-puBpdR0798OZvTTbP4A8Ix/l+A4dHDD0DGqYW6RQ+9jxkRFclaxxQb/SJAWZfWAkuyeQUytO7+7N4QKrDh+drA=="
            crossorigin=""/>
    <script src="https://unpkg.com/leaflet@1.3.4/dist/leaflet.js"
            integrity="sha512-nMMmRyTVoLYqjP9hrbed9S+FzjZHW5gY1TWCHA5ckwXZBadntCNs8kEqAWdrb9O7rxbCaA4lKTIWjDXZxflOcA=="
            crossorigin=""></script>
</head>
<body onload="init()">

    <div id="mapid" style="position: absolute; top:0; left:0; right:0; bottom:0;"></div>

    <script>
        function init(){
            
            //米原駅の緯度/経度
            var mpoint = [35.314933, 136.290105];
            var basho = "「JR米原駅」 待ち合わせ場所はここだよ！"

            var map = L.map('mapid',{zoomControl:false}).setView(mpoint, 16);
            
            var osm = L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
                            attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a>',
                            maxZoom: 18});
            
            osm.addTo(map);

            //マーカーを地図に追加
            L.marker(mpoint,{title: basho}).addTo(map);
        }
    </script>
</body>
</html>
```
## 表示

このような表示になります。

![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94029/3bfa6c13-4936-458e-fc85-ff99502ecfd9.png)
EtherscanのAPIキーを取得する方法の備忘録

## Etherscan

EtherscanはEthereumのブロックエクスプローラーです。今回は開発者としてEtherscanを利用する際によく使用するAPIキーの取得方法を備忘録としてまとめます。

まずは[Etherscan](https://etherscan.io/)にアクセスします。

![Screen Shot 2022-02-02 at 20.45.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258956/57488409-b776-408c-28a0-5d75f92cf1ff.png)

右上のSign Inをクリックします。

[サインインページ](https://etherscan.io/login)に遷移するので、アカウントがある場合はサインイン、ない場合はSign Upからアカウントを作成します。

![Screen Shot 2022-02-02 at 20.46.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258956/5946c390-cf7d-21e8-4426-4a1611c444bf.png)

[アカウントページ](https://etherscan.io/myaccount)に遷移するので、左下のAPI Keyをクリックします。

![Screen Shot 2022-02-02 at 20.47.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258956/751dfea6-35cc-8e9a-70ec-b71576e5f542.png)

[APIキーのページ](https://etherscan.io/myapikey)に遷移するので、Addをクリックします。

![Screen Shot 2022-02-02 at 20.50.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258956/c9525d30-257f-2e17-ee04-a5b1cf418ef2.png)

モーダルが出てくるので、任意のApp Nameを設定し、Create New API Keyをクリックします。

![Screen Shot 2022-02-02 at 20.50.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258956/a55ff5a4-a03b-6e2e-e67b-06310d925353.png)

これでAPI Keyの作成は完了です。My API Keysに表示されているAPI Keyを利用することができます。
JavaScriptでLabelの値を取得する方法（初心者のつまずき）TextBoxの値が消える謎

##TextBoxをLabelに変えたところ、JavaScriptで値が取得できなくなった
getElementById("txtDispYM").valueという記述のままでは、
値が取得できずundifinedとなってしまった。
（TextBoxでは問題なかった）

##解決策
JavaScriptでの取得をInnerTextにした。
InnnerHTMLでもうまくいった。

やはり.Value .Textだと取得できませんでした。


```javascript
//以下の記述でidがtestのlabelの値を取得できた。
if (!confirm(document.getElementById("test").innerText + " の処理を実行します。よろしいですか？"))
```

調べると、ClientIDMode="Static"を指定するというのもありましたが、
私の場合は無しで大丈夫でした。
asp.netのバージョンによるのでしょうか、

##結論と参考サイト
getElementById("test").innerText にすればよいだけ。


https://okwave.jp/qa/q8850992.html


https://atmarkit.itmedia.co.jp/bbs/phpBB/viewtopic.php?topic=45441&forum=7

##どうしてもLabelにしたかった理由
以下はIEからEdgeへ移行する作業を行なってる時に起きたのですが、
いまいち何で、何がいけないのかよくわかりません。
プログラム全体を見つくせてないので、どこかの記述が影響してるのか、
それともそういうものなのかが分かりませんでした。
調べても出てこなかったので、触ってるプログラムの問題かもしれないです。

##Edge移行で、TextBoxの値がクリックで消える現象が起きた

IE11→Edge移行した際に、テーブルの中にテキストボックスを設置したところ、
なんだか消えてしまう現象が起きました。
テキストボックスはReadOnlyで、データベースから取得した値を表示するのみです。
（なので別にラベルでも良い）

<h4>イメージ</h4>

TextBoxの中身をマウスで選択して、少し離れたところ（おそらくtdの青いところ）を
押すと、TextBoxの表示が消える

![スクリーンショット 2022-02-02 20.27.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2348805/3b39954e-db6f-726c-8947-43000c3f790a.png)


```html
<div style="position:absolute; top:100px; left:350px; width:300px;">
        <table style="border-style:none;" border="1" cellpadding="0" cellspacing="0">
            <tr style="height:50px;">
    	        <td>ほげほげ</td>
                <td>
                 <asp:TextBox ID="test" runat="server" CssClass="txt2" Width="150px" ReadOnly="true" TabIndex="-1"/>--%>
                </td>
	        </tr>
        </table>
</div>
```

##TextBoxからLabelに変更


TextBoxではなく、Labelにしたらその現象は起きなくなりました。

```c#
<asp:Label ID="test" runat="server" CssClass="txt2" Width="150px" TabIndex="-1"></asp:Label>
```
Tableでなくdivで囲ってみたり、z-index付けたりと色々試行錯誤しましたが、
やはり消えてしまいした。
TextBoxの高さを大きくしてみたりもしましたが、何だか見た目が崩れてきたので
諦めてLabelにしたところ、消える現象はなくなりました。

うーん。何が悪さをしてるのか気にはなるのですが・:frowning2:・・。
Yahooのニュース一覧をpythonでスクレイピングする方法

#1.環境
python3
mac OS
モジュールのrequestsとbs4が導入済であること

#２.モジュールを導入してない場合
macの場合、以下の文をターミナル入れれば大丈夫！

```
pip install requests
pip install bs4
```
#3.取得箇所

赤枠で囲ったところ
![スクリーンショット 2022-02-02 20.30.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2509814/986251a6-b90a-0dda-9ae8-4e910a2e9285.png)


とりあえず、モジュールを導入したら、以下のコードを実行する

```sample.py
import requests
from bs4 import BeautifulSoup

# ニュースサイトを入れる
URL = "https://news.yahoo.co.jp/"
# HTMLのデータを取得する
res = requests.get(URL)
# BeautifulSoupで操作できるようにデータを変換する
soup = BeautifulSoup(res.text, 'html.parser')

# ニュースを記載している箇所のクラス名を取得し、取得数だけ作業を繰り返す
for val in range(len(soup.find_all(class_="sc-hmzhuo dxywt"))):

    # ニュースのタイトルを取得
    news_tittle = soup.find_all(class_="sc-hmzhuo dxywt")[val].get_text()
    # ニュースのURLを取得
    news_herf = soup.find_all(class_="sc-hmzhuo dxywt")[val].find_all('a')[0].get("href")

    # 取得データを出力する
    print(val + "番目")
    print("--------------------------")
    print(news_tittle)
    print(news_herf)


# モジュールを終了させる
res.close()
soup.clear()

```
結果は、以下のように出ます。
例
1　番目
ーーーーーーーーーーーーー
ニュースのタイトル
ニュースサイトのURL
２　番目
ーーーーーーーーーーーーー
ニュースのタイトル
ニュースサイトのURL
.
.
.
KeyVaultに格納したシークレットをAKSで読みだす Managed Identity 編

AKSクラスタとKeyVaultを連携するにはakv2k8sを使うのが楽らしい。

akv2k8sの公式ページ
https://akv2k8s.io/

が、[チュートリアル](https://akv2k8s.io/tutorials/prerequisites/) を見ると

```sh
az keyvault set-policy -n akv2k8s-test --spn <spn for akv2k8s> --secret-permissions get 

```

とAKSに ``Service Principal`` が付与されている前提で、読みだしパーミッションを設定されていらっしゃる。
Azureはニワカなのでよくわかっていなかったのですが、Azure内で完結するなら、システム割り当ての ``Managed Identity`` 使って認証するのがよさそうです。

Azure リソースのマネージド ID とは
https://docs.microsoft.com/ja-jp/azure/active-directory/managed-identities-azure-resources/overview


AKSクラスタの ``Managed Identity`` は、クラスタを作成した際に自動的に追加される、リソースグループ ``MC_[リソースグループ名]_[クラスタ名]`` 内にある、 種類マネージドIDの ``[クラスタ名]-aks-agentpool`` を見ると書いてあります。
``オブジェクト(プリンシパル)ID`` というやつですな。 カッコ書きとかいらないので、呼称を統一してほしい...


あとは、KeyVaultのアクセスポリシーに、調べた ``Managed Identity``でシークレット読み出し権限を付与するだけ。
わかってみれば簡単でした。



- 参考

Azure Kubernetes ServiceのPodからManaged identities(旧MSI）認証でKey Vaultの情報を取得する

https://yomon.hatenablog.com/entry/2020/04/aks_msi
はじめてのSlackAPI

## なりゆき
後輩　「アプリ作りはじめたんすよ」
わたし「いいね〜」
　　　「どんなアプリ？」
後輩　「社内交流を活発化するアプリっす」
わたし「いいね〜」
後輩　「でも困ってることがあって…」
わたし「ん？なになにどうした？」
　　　（先輩風吹かせる気まんまん）
後輩　「Slackのユーザデータ使いたくて」
　　　「SlackのユーザIDとプロフ画像がほしいんです」
わたし「それならSlackAPIを使えばできるんじゃね？」
後輩　「SlackAPIってどうやって使うんですか？」
わたし「どうだっけ？ちょっと調べてみようぜ」
　　　（前ボット作ったときもAPI叩いてちょい！だったからいけるでしょ・・・）

## やりたいこと
- SlackのユーザIDを取得する
- Slackのユーザプロフィールに設定されている画像を取得する

ユーザアクセストークンは取得出来ている前提で、各SlackAPIを叩けてるように調査する

## みちのり

まずSlackAppを作成する、他サイトを参考にさくっと。
名前はとりあえず「CoffeeBreak」にしとく
（コーヒー飲んでる間にちゃちゃっと終わるでしょ）

今回対象となるSlackAPIは次の２つ

- [users.identity](https://api.slack.com/methods/users.identity)
：ユーザのSlackIDを取得する（users.infoに必要、後々はアプリからDM送りたい）
- [users.info](https://api.slack.com/methods/users.info)
：プロフ画像ほしい

叩きたいAPIに合わせてトークンの権限を設定する
https://api.slack.com/methods を参考に以下の感じで設定。

UserOAuthToken（users:read）
BotUserOAuthToken（users:read,identity.basic）
![スクリーンショット 2022-02-01 17.33.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/815054/3f5f9b96-1887-c2bc-e331-8b50b5fbb352.png)

再インストール
![スクリーンショット 2022-02-01 17.33.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/815054/5ef34fed-67cb-73a3-a885-614bde4daede.png)

え・・・なんで？
![スクリーンショット 2022-02-01 17.34.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/815054/357c70d3-4a81-38f0-1bec-a0f8ec2e14b1.png)

調べてみると「users:read」と「identity.basic」を一緒に権限設定できなさそう
もしかしてと思って「identity.basic」権限だけで「users.identity」を叩いてみる
![スクリーンショット 2022-02-01 17.37.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/815054/00bd625f-f523-5fdb-93ef-1a11b2aab6ec.png)


イケルヤン＼＼\\٩( 'ω' )و //／／
どうやら権限はこれでいけそう
よしこのままURLで叩いてみよう。トークンをパラメータ指定して・・・

```
https://slack.com/api/users.identity?token=xoxp-XXXXXXX
```

おりゃ！
![スクリーンショット 2022-02-01 18.16.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/815054/b345e11d-3357-8c6f-b267-57124f861a4e.png)
(　ﾟдﾟ)なんで認証できんの？

https://api.slack.com/web
>Pass arguments as:
GET querystring parameters
POST parameters presented as application/x-www-form-urlencoded
or a mix of both GET and POST parameters

なるほど、ヘッダーにセットしてから投げる方法もあるのね.
セットして投げてみると
![スクリーンショット 2022-02-01 19.28.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/815054/e061d606-1677-cf8f-b46d-27ddde925aea.png)
イケタヤン＼＼\\٩( 'ω' )و //／／
よしこれでSlackAPIを叩ける目処はたった

## おわりに
後輩　「なんとかいけそうですね」
わたし「わたしやから当然！！」
後輩　「でも、なかなかすんなりとはいきませんでしたね」
わたし「ま、今回はたまたまはまっただけ、たまたm」
後輩　「他のAPI使う時もまた相談させてください！！」
　　　「では〜〜」
わたし「(　ﾟдﾟ)」
END
.git化するディレクトリを間違えたときの対処法


###【before】.git化するディレクトリを間違えた
すでにリモートリポジトリで数回コミット＆プッシュ済み
![スクリーンショット 2022-02-01 6.32.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/618527/0d98aa25-85f1-619c-718e-ebf98a9e0a93.png)
###【After】 ひとつ上のディレクトリで.git化
リモートリポジトリのgithubツリーをローカルに合わせた
![スクリーンショット 2022-02-02 19.53.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/618527/52653b86-18ee-e0b8-c73b-84f450bdc811.png)


###やったこと
###.gitフォルダ削除
.gitignore
.swiftlint.yml
上記２ファイルは削除しないで正しいディレクトリに移動した方が楽。
###正しいディレクトリで% git init
からいつもの流れでコミットまで行う。
###git pushがreject（拒否）される

```
○○ % git push origin main                      
To github.com:○○/○○.git
 ! [rejected]        main -> main (non-fast-forward)
error: failed to push some refs to 'github.com:○○/○○.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
```

今回はローカルの内容をリモートリポジトリに反映させたいので
###強制(force)プッシュ

```
% git push -f origin main  　強制(force)的にプッシュする
```

###githubツリーに残った不要なキャッシュを消す

```
% git rm -r --cached . //ファイル全体キャッシュ削除
```

最初はリモートリポジトリごと削除して新規作成したくなったが思い留まって修正できたのでよし！
Rider + Unityの連携がうまくいかない時のメモ

RiderをIDEとして利用時にうまく連携がいかなくなった時があり、その時の対処法を備忘録として残す

## 開発環境
macOS Monterey 12.2
Unity 2021.2.9f1(Apple silicon)
Rider 2021.3.3

## Unity側対応
- PackageManagerで `JetBrains Rider Editor` をインストールする
![JetBrains Rider Editor](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236499/950cfa3a-251b-ea32-4767-f483ad57d558.png)

- PreferencesのExternal Scripts EditorでRiderを指定する
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236499/aa8fde62-26cf-d954-3f46-7fad4aae718a.png" width=75%>

## Rider側対応
- .NET Core、Monoのインストール
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236499/0a7b21d5-38c8-6529-b140-703803bcc6c5.png" width=70%>

- Mono、.NET Coreのインストールパスの確認
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236499/f1348f39-d0c8-8b4e-c8c5-e373187718e4.png" width=70%>


## 結論
自分は、旧MBP→新MBP移行時になぜかMonoのインストールデータだけが飛んでおりそちらを入れ直した。ただMonoのファイルパスは旧パスのままになっているのに気づかずだいぶ遠回りな作業をしてしまった。。。
小ネタシリーズ: Reactを理解したつもりになれる和訳集

__React__(和訳)...
反応する、作用し合う、反応を示す、反対する、反抗する、(悪い)反応を示す、(…に)反応する



__State__(和訳)...

状態、ありさま、様子、興奮状態、威厳、威儀、荘厳、公式、国家、国



__Prop(s)__(和訳)...

支柱、つっぱり、つっかい棒、支え、頼り、支持者、プロップ、プロペラ、小道具

ではなくて...



__Propertie(s)__に直してから...

財産、資産、所有物、所有地、地所、土地(建物)、所有権、所有、(ものの)特質、特性



__Store__...

(小売りの)店、商店、大きな店、雑貨店、デパート、(食料などの)蓄え、貯蔵、備え、(陸軍・海軍などの)衣食の備え、用品



__JSX__...

Javascript XML(EXtensible Markup Language)(え？
[Swift] Doubleを少数第x位まで表示させる方法

##はじめに

今回解決したい問題は、
Double型の**3.5 → 3.50** という表示にする方法です。
これだけではイメージが湧きにくいので具体例を用いていきましょう。

##前提

Double型は少数を扱えるのですが、例えば下記のようなDoubleをStringに変換するコードの場合、
不要な０（この場合、少数第二位）が勝手に切り捨てられてしまいます。

```swift
let hoge: Double = 3.50
let string = String(hoge)

print(string)
//3.5　　←０が切り捨てられてしまいます。
```
これでは何かの平均をだして少数第x位まで省略せずにlabelに表示させたいなどのユースケースの場合にとても不便です。

そのような場合に下記の処理を書くことで解決できます。

```swift
let hoge: Double = 3.50
let string: String = String(format: "%.2f", hoge)

print(string)
// 3.50　　　←0が切り捨てられない。
```

ここの**%2f**というとこで少数第二位まで有効にすることを制御しています。
つまり少数第三位まで表示させたい場合は**String(format: "%.3f", hoge)**とするだけです。

:::note warn
これらの処理は四捨五入をするものではありませんのでご留意を。
四捨五入や少数点の切り捨て上げを行い処理は
round() → 四捨五入
floor() → 切り捨て
ceil() → 切り上げ
などを使うのでごっちゃにならないようにしましょう。
:::

##Extensionで汎用的にする

```swift
extension String {

    static func decimalNumber(index: Double, number: Int) -> String {
        return String(format: "%.\(number)f", index)
    }
}

//呼び出し側
let hoge: Double = 3.50
let string = String.decimalNumber(index: hoge, number: 2)

print(string)
//3.50
```

こんな感じでextensionを定義してあげると他の場面での応用が効くと思います。
最後までご覧いただきありがとうございました！
Laravel(PHP)でCRUDアプリを作成し、AWSにデプロイする

## Laravelのversionについて
- "8"を使用する

## 構成
- Laravel
- NGINX
- MYSQL

## 機能要件
- ユーザー登録 && ログイン
- ユーザーCRUD
- 記事CRUD
- 他ユーザーのフォロー
- フォローユーザー一覧
- フォロワーユーザー一覧
- 記事へのコメントCRUD
- 投稿記事の検索機能

## 開発環境を構築する
- ymlファイルにDockerの構成を記述
- `build`あとにコンテナ内でLaravel環境を構築
 - `composer create-project --prefer-dist laravel/laravel {$APP_NAME}`

### Nginxの設定につまずく
- css, js ファイルが上手く読み込めない　→　docker-compose.ymlのappのvolumesをbuild後に変更したために、ファイルの読み込みがうまくいかなかった

## DBの要件定義

|Users||
|:-|-:|
|id|UUID|
|name|VARCHAR|
|email|VARCHAR|
|email_verified_at|DATETIME|
|password|VARCHAR|
|remember_token|BOOLEAN|
|created_at|TIMESTAMP|
|updated_at|TIMESTAMP|

|Articles||
|:-|-:|
|id|UUID|
|user_id|UUID|
|title|VARCHAR|
|category|VARCHAR|
|content|VARCHAR|
|created_at|TIMESTAMP|
|updated_at|TIMESTAMP|

|Comments||
|:-|-:|
|id|UUID|
|user_id|UUID|
|article_id|UUID|
|content|VARCHAR|
|created_at|TIMESTAMP|
|updated_at|TIMESTAMP|

|Likes||
|:-|-:|
|id|UUID|
|user_id|UUID|
|article_id|UUID|
|created_at|TIMESTAMP|
|updated_at|TIMESTAMP|

|Follow_Users||
|:-|-:|
|id|UUID|
|user_id|UUID|
|following_user_id|UUID|
|created_at|TIMESTAMP|
|updated_at|TIMESTAMP|

- UUID使用のため`composer require goldspecdigital/laravel-eloquent-uuid:^8.0`を追加する

## 機能を実装する
- livewireを使ってみる
 - SPAのような動きができる(bladeテンプレートが使える)
- 日本語化する
 - `resource/lang/`内に日本語化のファイルを配置する

### error: Property ['{取得したい値}'] does not exist on this collection instance.
- DBからのデータは、取得の方法に応じて、toArrayメソッド等を使用し変換する必要がある

## AWSにデプロイする
- Light Sailを使用する
 - インスタンス作成(LANP構成)
 - DNS作成
 - ディストリビューション作成
 - HTTPS化する
 - インスタンスに`git clone`する
 - `/opt/bitnami/apache2/htdocs/`配下にディレクトリを配置する
 - `/opt/bitnami/apache2/conf/bitnami/bitnami.conf`を編集し、ディレクトリの階層を上記に修正する
 - bootstrapファイル等をbuildするためにnode.jsをinstallする
 - `.env`を編集する（`ASSET_URL`を追加し、https化したドメインを入力しasettsファイルが上手く読み込めるようにする）


### POST時にブラウザで”保護されていない通信~”が表示されてしまう
- `app/app/Providers/AppServiceProvider.php`を編集する
RHEL8でisoイメージからレポジトリーを作成する

##目標
 - インターネットアクセスができない環境でRHEL8にyum(dnf)を使ってパッケージをインストールする

##環境
 - RHEL8.5

##準備するもの
 - RHEL8.5のメディアISOを事前に/tmp/isoにUpload

##SUBSCRIPTION MANAGER リポジトリーの無効化
```shell-session
# cp -ip /etc/dnf/plugins/subscription-manager.conf /etc/dnf/plugins/subscription-manager.conf-org
# sed -i -e "s/enabled=1/enabled=0/" /etc/dnf/plugins/subscription-manager.conf
```
##RHEL DVDレポジトリー作成
DVDをマウント
 ※OS再起動後も使用する場合は/etc/fstabにも登録する

```shell-session
# mount -o loop -t iso9660 /tmp/iso/rhel-8.5-x86_64-dvd.iso  /media/
```
レポジトリーファイル作成

```shell-session
# vi /etc/yum.repos.d/rhel8-dvd.repo
```
以下の内容をrhel8-dvd.repoに書く

```shell-session
[BaseOS]
name=RHEL8 - BaseOS
metadata_expire=-1
gpgcheck=1
enabled=1
baseurl=file:///media/BaseOS/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release

[AppStream]
name=RHEL8 - AppStream
metadata_expire=-1
gpgcheck=1
enabled=1
baseurl=file:///media/AppStream/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release
```
レポジトリーのキャッシュをクリアー

```shell-session
# yum clean all　又は # dnf clean all
# yum repolist -v　又は # dnf repolist -v
```
yum(dnf) repolist repolist -vで以下の結果がでればOK

```shell-session
Loaded plugins: builddep, changelog, config-manager, copr, debug, debuginfo-install, download, generate_completion_cache, groups-manager, kpatch, needs-restarting, playground, product-id, repoclosure, repodiff, repograph, repomanage, reposync, subscription-manager, uploadprofile
Updating Subscription Management repositories.
Unable to read consumer identity

This system is not registered with an entitlement server. You can use subscription-manager to register.

YUM version: 4.7.0
cachedir: /var/cache/dnf
Last metadata expiration check: 0:00:13 ago on Wed 02 Feb 2022 07:59:04 PM JST.
Repo-id            : AppStream
Repo-name          : RHEL 8 - AppStream
Repo-revision      : 1634110997
Repo-updated       : Wed 13 Oct 2021 04:43:19 PM JST
Repo-pkgs          : 6,220
Repo-available-pkgs: 5,282
Repo-size          : 8.2 G
Repo-baseurl       : file:///media/AppStream/
Repo-expire        : Never (last: Wed 02 Feb 2022 07:59:04 PM JST)
Repo-filename      : /etc/yum.repos.d/rhel8-dvd.repo

Repo-id            : BaseOS
Repo-name          : RHEL 8 - BaseOS
Repo-revision      : 1634111018
Repo-updated       : Wed 13 Oct 2021 04:43:39 PM JST
Repo-pkgs          : 1,708
Repo-available-pkgs: 1,706
Repo-size          : 1.2 G
Repo-baseurl       : file:///media/BaseOS/
Repo-expire        : Never (last: Wed 02 Feb 2022 07:59:03 PM JST)
Repo-filename      : /etc/yum.repos.d/rhel8-dvd.repo
Total packages: 7,928
```
eb deploy 時に `Service:AmazonCloudFormation, Message:Template error` が発生

# 概要

デプロイ時にこんなエラーが出る。

```text
2022-02-02 10:57:08    ERROR   Service:AmazonCloudFormation, Message:Template error: every Fn::Join object requires two parameters, (1) a string delimiter and (2) a list of strings to be joined or a function that returns a list of strings (such as Fn::GetAZs) to be joined.
```

# 原因

自分の場合、ebextensions の commands に以下コメントを入れてしまっていたからだった。

```bash
#!/bin/bash
  :
# convert `{"hoge": "fuga"}` => `export hoge='fuga'`
```

バッククオートが無意味に展開されてしまい、CloudFormation の意図しないエラーが発生したのだと考えられる。

# 対策

カッコつけないで、バッククオートをとる。

```bash
# convert {"hoge": "fuga"} => export hoge='fuga'
```

# 備考

Elastic Beanstalk というよりは Bash 的な話。初心を忘れてはいけない。
Figma でスクロール可能なモーダルを作成する

# 完成形

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/488228/ea1e75f0-310b-6afb-510c-6f2db44ba622.gif" width=60%>

# 手順

1. ボタンとモーダルを設置する
2. ボタンクリック時にモーダルが開くようにする
3. バツボタンをクリックでモーダルが閉じるようにする
4. モーダル内のコンテンツをスクロール可能にする

## 1. ボタンとモーダルを設置する

![スクリーンショット 2022-02-02 18.34.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/488228/b3f50a16-3e56-596f-b285-ca05132ae085.png)

## 2. ボタンクリック時にモーダルが開くようにする

![スクリーンショット 2022-02-02 18.41.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/488228/e210f6ba-9612-798b-d69c-b5d790d5e3e9.png)

## 3. バツボタンをクリックでモーダルが閉じるようにする

![スクリーンショット 2022-02-02 18.45.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/488228/f74a7e72-7eb6-832e-6b25-d061814405b1.png)

## 4. モーダル内のコンテンツをスクロール可能にする

コンテンツ (あいうえお…) の親要素として Frame を配置し、`Clip content` のチェックをオンにする。

![スクリーンショット 2022-02-02 18.50.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/488228/75255b73-a55b-ed27-7747-734587425588.png)

続いて `Overflow scrolling` を `Vertical scrolling` にする。

![スクリーンショット 2022-02-02 18.56.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/488228/f70e9b57-6c0b-eade-8c4c-8d3fda06cf0e.png)

# 完成

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/488228/ea1e75f0-310b-6afb-510c-6f2db44ba622.gif" width=60%>
【初心者向け】ActiveRecord::Enumの使い方

#ActiveRecord::Enumとは
ActiveRecordが提供しており、列挙型もしくはenum型と呼ばれます
enum型は数値カラムをプログラム上で別名を使って扱うことができます

#どういうときに使うのか
例えば、Productモデルに販売状況カラム（完売、販売中、予約中）を追加するときなど、カラム値のパターンが少ない場合に使います。

#使うための準備
* カラム作成
* 名称を定義
* ja.yml設定

##カラム作成
enum設定したい数値型のカラムを作成します

```terminal:ターミナル
bin/rails g migration AddStatusToProduct status:integer
bin/rails db:migrate
```
##名称を定義
以下のように記述すると、
ハッシュのようにシンボルと値を紐付けることができます

```ruby:app/model/product.rb
enum status: { sold_out: 0, sale: 1, reservation: 2 }
```
##ja.yml設定
###デフォルト言語を日本語に設定
```ruby:config/application.rb
  class Application < Rails::Application
    config.i18n.default_locale = :ja
  end
```
###翻訳ファイルを編集
```yml:config/locales/ja.yml
ja:
  enums:
    product:
      status:
        sold_out: '完売'
        sale: '販売中'
        reservation: '予約中'
```
#便利なメソッド
##確認メソッド
以下のようにenumの値を確認することが可能です

```terminal:ターミナル
product = Product.new(name: "pen", status: :sold_out)

product.sale? #statusがsaleか
=> false

product.sold_out? #statusがsold_outか
=> true
```
##更新メソッド
以下のようにenumの値を更新することが可能です

```terminal:ターミナル
product = Product.new(name: "pen", status: :sold_out)

product.sale?
=> false

product.sold_out! #statusをsold_outに更新

product.sold_out?
=> true
```
#日本語で表示
##viewの場合
```ruby:app/controller/products_controller.rb
def show
  @product = Product.find(1)
end
```
```erb:app/view/products/show.html.erb
# tメソッドはja.ymlを参照し、翻訳した値を返却します
<%= t("enums.product.status.#{@product.status}") %>
```
本来、tメソッドの頭に「I18n」 を付ける必要があるのですが、railsガイド(https://railsguides.jp/i18n.html) で以下のように説明されています。
> Railsはt(translate) ヘルパーメソッドを自動的にビューに追加するので、I18n.tのようにフルスペルで書かずに済みます。

##ターミナルの場合
```terminal:ターミナル
I18n.t("enums.product.status.sold_out")
```

#参考
https://pikawaka.com/rails/enum

https://pikawaka.com/rails/i18n

https://www.amazon.co.jp/%E3%83%91%E3%83%BC%E3%83%95%E3%82%A7%E3%82%AF%E3%83%88-Ruby-Rails-%E3%80%90%E5%A2%97%E8%A3%9C%E6%94%B9%E8%A8%82%E7%89%88%E3%80%91-Perfect/dp/4297114623
【Ruby】二次元配列に要素を代入すると、他の要素まで変更されてしまうときの対処法

# **二次元配列への代入がうまくいかない**

3✕3の二次元配列をつくります。


```ruby
a = Array.new(3, Array.new(3, 0))
p a # => [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
```

見た目上は問題ありませんね。二次元配列ができています。

ですが、要素を代入してみると、

```ruby
a[0][0] = 1
p a # => [[1, 0, 0],[1, 0, 0],[1, 0, 0]]
```

あれ...？？1つ目の配列の1番目に要素を代入したはずですが、全ての配列の1番目が変わってしまっています。

オブジェクトIDを確認してみましょう。

```ruby
p a.map(&:object_id)
# => [60, 60, 60]`
```
3つの配列とも同じオブジェクトになっちゃってます。これが原因っぽい。

# **対処法**

mapメソッドを使うことで、上記の問題を回避できます。

```ruby
a = Array.new(3).map { Array.new(3,0) }

p a # => [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
```
要素を追加してみます。

```ruby
a[0][0] = 5
p a # => [[5, 0, 0], [0, 0, 0], [0, 0, 0]]
```
はい！ ちゃんと、第一要素にだけ代入することができました！

念の為オブジェクトIDも確認しておきましょう。

```ruby
p a.map(&:object_id)
# => [60, 80, 100]
```

さっきと違って、それぞれの配列を別のオブジェクトとして定義できています。

ちなみに、ブロックで渡すだけでもうまくいきます。↓

```ruby
a = Array.new(3) { Array.new(3,0) }
```

# **原因**

[Rubyリファレンスマニュアル](https://docs.ruby-lang.org/ja/latest/method/Array/s/new.html) から引用

> new(size = 0, val = nil) -> Array
> 
> 
> 長さ size の配列を生成し、各要素を val で初期化して返します。要素毎に val が複製されるわけではないことに注意してください。全要素が同じオブジェクト val を参照します。後述の例では、配列の各要素は全て同一の文字列を指します。
> 

間違った方法↓

```ruby
a = Array.new(3, Array.new(3, 0))
p a # => [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
```
これは、newメソッドで長さ3の配列をつくって、中の要素に`Array.new(3.0)`を代入(初期化)しています。

しかし、`Array.new(3.0)`が3つ複製されているわけではなく、1つのオブジェクトを3回参照しているだけですので今回のような問題が起こってしまいました。

値は同じでも、同じオブジェクトなのか違うオブジェクトなのかを意識することが大切ですね。
VSCode のショートカットの調べ方

## VSCode で探す

コマンドパレットで `open keyboard shortcuts` を入力すると検索一覧が表示されます。

コマンドパレットのショートカット

- macOS: `cmd + shift + p`
- Windows: `ctrl + shift + p`

![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19223/844bcbe9-9210-b0b9-8b3e-eb242dd1b48f.png)

検索フォーム内で文字検索したり、右にあるキーボードアイコンをクリックした `キーを記録` でショートカット入力で検索もできます。


## Qiita で探す

https://qiita.com/12345/items/64f4372fbca041e949d0
Mongo CompassからMongoDB Atlasに接続できない問題（connection stringを使うケース）





問題
-
Mongo CompassからMongoDB Atlasに、connection stringで接続する際に、以下のように表示され接続できませんでした。
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/425910/9599cb65-7997-b17a-c66d-e8de13fbd000.png)

`querySrv ETIMEOUT _mongodb._tcp.cluster0.5zhom.mongodb.net`


解決策
-

mongo compass専用のconnection stringではなく、applicationから接続する際のconnection stringを使うと接続できるようです。
それも、node.jsのmongoDB用のドライバーであるmongodbというパッケージのバージョン2.2.12用のもので接続できます。

mongo atlasの管理画面から「Connect」をクリック

![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/425910/ea64b407-dd00-499d-92e9-ea1b324a8de5.png)

mongo compassではなく、アプリケーション用のほうを選択
![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/425910/7c4ce72d-af08-3f85-80c7-17544a7737cf.png)

Node.jsの「2.2.12 or later」を選択
![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/425910/dcbce898-9dae-06d4-ee85-870e4d8acba6.png)

connection stringの`<password>`となってる箇所をmongoDBのユーザのパスワードに変更。
さきほどのフォームにいれると無事接続完了です🎉
![スクリーンショット 2022-02-02 19.36.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/425910/4d2a3b6f-06c4-2166-18b6-5d2859e10dfc.png)
AWS SESで大量メールを送る際のbounce対策にはサプレッションリストを使おう

# AWS SESで大量メールを送る際のbounce対策

## 概要
AWS SESを使用して、数千件のメールを送信する処理を使用する際にbounce対策が必要となり、bounceで届いたメールをSESが受け取ったらSQSで通知して...DynamoDBにいれ...
再送信時にDBを見て...的な対応をしようと思っていたらAWSが素晴らしい機能を作っていてくれたのでメモ

### bounceとは

bounceメールとは何らかの理由で送信先に届かなかったメールのことです。
bounceになってしまう理由はいくつかあり、大きく分けて2種類のbounceがあります。

#### ソフトバウンス
- 一時的な問題でメールが送信先に届かない<br>
-　受信側のメールサーバーの容量がいっぱい<br>
-　受信側のメールサーバで障害が起こっている　etc...

#### ハードバウンス
- 恒久的な何かしらの問題でメールが届かない
  - メールアドレスが無効になっている
  - メールアドレスが存在しない　etc...

※ AWS SESはハードバウンスのみでソフトバウンスはバウンス率にはカウントされないようです。

### なぜ、bounceの対策をしないといけないのか
送信したメールに大量のbounceが発生すると、バウンス率が高くなります。
そして、一定の水準を超えるとそのアカウントのメール配信が停止されてしまうからです。

```
Q4. アカウントのレビューや、送信の一時停止の原因になりえるバウンス率は公開されていますか？
最良の結果を得るには、バウンス率を 2% 未満に維持する必要があります。これより高いバウンス率は、E メールの配信に影響する可能性があります。

バウンス率が 5% 以上になると、アカウントはレビュー対象になります。バウンス率が 10% 以上の場合は、高いバウンス率の原因となった問題が解決するまで、以後の E メール送信を一時停止することがあります。
```

![supression.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421982/9f36a55e-c48c-e7df-7b13-650dd10aa59b.png)


↑こちらの画像では0.03%(bounceメールの対策済み)なので問題はないのですが、大量にメールを送る機能を作成した場合などは、予め対応をいれることをおすすめします。

## サプレッションリストを使おう

実際に、bounceメールの対策として以下のような対応をしようといわれます。

```
宛先リストをクリーンに保つ。
不正なメールアドレスや、反応の一切ない受信者は定期的に宛先リストから削除 etc...
```
この仕組、どうやって作ったらいいんだ？？
先に概要に上げたように送信できなかったメールアドレスをDBnに格納してメール送信のたびにチェックさせたらいいんだっけ？？となりますが、サプレッションリストを使うと...

![setting.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421982/1b359a20-5482-7b1d-321f-829ce895509a.png)

 上のように２つの設定を有効にするだけで

![sup.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421982/e8cba4fc-58f7-a028-4dc6-93fab400691e.png)

supression listにbounceで返ってきたメールアドレスが追加されていきます。
このsupression listに追加されたメールアドレスにはSESからはメールが送られないようになっているので、
SES経由でのメール送信する場合は、これ以上特に設定をする必要が有りません。


```
aws sesv2 list-suppressed-destinations --region ap-northeast-1 --profile aaa
```
↑のCLIコマンドでsupression listに登録されているアドレスを一括で　取得でき

```
aws sesv2 list-suppressed-destinations --start-date 1604394130
```
`--start-date`や`--end-date`と組み合わせると、いつ、どのメールアドレスがsupression listに登録されたかなど簡単に抽出できるため万が一ユーザにメールが届かないなどの問い合わせがあった場合も
supression listに登録されていないかすぐに確認することができます。

参考
[Amazon SES 送信レビュープロセスに関するよくある質問](https://docs.aws.amazon.com/ja_jp/ses/latest/DeveloperGuide/faqs-enforcement.html)
wordleの平均解答数はどのくらいになるのか？

最近wordleばっかりみるのだけど、あんまり自分が解いたもの平均以下なのかどうかがわからない.
アルゴリズム作って解くと、どのくらいで解けるのかが気になったので雑に調べてみた.
アルゴリズムやwordleのルール解釈にミスがあるかもしれないので、参考程度にしてもらえれば :pray: 

⚠️⚠️ wordleを解くのがつまらなくなるかもしれないので、楽しみたい方は見ないことをおすすめします ⚠️ ⚠️ 

↓↓


## wordle solver

https://gist.github.com/mattak/4016b0f62f0af55aa9dfe6cc9486c67f

ロジック的には簡単なものを採用した. (高頻度な文字を含むワードから順に選び除外するアルゴリズム)
条件を厳しくしたりすると、もっと賢く解けるかもしれないので、あくまで参考程度に.

1. 単語リストから文字出現頻度を調べる.
1. 単語ごとに文字出現頻度の合計値を算出 (apple => aの頻度+pの頻度+pの頻度+lの頻度+eの頻度)
1. 単語ごとの出現頻度合計値でソートする
1. 頻度が高く、各文字がかぶらないワードを選出　(できる限り候補を絞るため)
1. 選出ワードで解く
  1. word, answer_wordを比較して、緑(1), 黄(0), 黒(-1)の3パターンを結果として返す. 例: purse <=> sugar であれば [-1,1,0,0,-1]
1. 単語リストを下記条件でフィルタリングする.
  1. 緑(1): 同じ文字を含み同じ位置にあるものでフィルタ
  1. 黄(0): 同じ文字を含み違う位置にあるものでフィルタ
  1. 黒(-1): 同一文字の出現数が1の場合は、該当文字を含まないようにフィルタ. (２文字以上同じ文字を含み、正解が1文字の場合、1文字目しか黄色にはならない)
1. 解いた結果が[1,1,1,1,1]になるまで、4-6を繰り返す

## 実行

ワードリストは下記のものを採用した
- http://www.mieliestronk.com/wordlist.html

```shell-session
## 改行文字含むので+1
$ cat corncob_lowercase.txt | awk '{if(length($1) == 6) print}' > words.txt
$ ruby solve_wordle.rb words.txt > words_result.txt
$ cat words_result.txt | awk '{s+=$2;t+=1}END{print s/t}'
4.26699
```

平均は4.27回だった.
上位/下位の単語を見てみる.

```
$ cat words_result.txt | sort -n -k2 | head
arose	1
actor	2
adios	2
adore	2
adorn	2
aesop	2
afire	2
afros	2
aides	2
aisle	2
$ cat words_result.txt | sort -n -k2 | tail
jails	10
jives	10
nines	10
sales	10
saves	10
fears	11
sails	11
wares	11
wears	12
sears	13
```

aroseが高頻度文字セットで互いに疎な感じっぽい.
せっかくなので、5文字以外でも調べてみた.

| letters | words | mean_solves |
|:------|:------|:------|
| 2 | 47 | 4.17021 |
| 3 | 589 | 6.11545 |
| 4 | 2294 | 5.03313 |
| 5 | 4266 | 4.26699 |
| 6 | 6936 | 3.70804 |
| 7 | 9203 | 3.40563 |


３文字を境に難易度が下がっていっているというのが面白い.
3文字って略語っぽいものもありそうだ.
こうみると程よく単語数もあって、簡単すぎない5文字っていうのは丁度いいゲームバランスなのかもしれない.
連続確率分布の期待値・分散・積率母関数まとめ【統計検定準1級対策】

#はじめに
この記事は統計検定準一級及び一級の合格に向けて作成されたものです。

#期待値・分散・積率母関数の定義
$f(x)$が確率密度関数、つまり$\int_{-\infty}^{\infty}f(x)dx=1$が成り立つとする。
このとき、$X$~$f(x)$とかく。
###期待値 E[X]
$E[X] = \int_{-\infty}^{\infty}xf(x)dx$
###分散 V[X]
$V[X]=E[(X-E[X])^2]=\int_{-\infty}^{\infty}(x-E[x])^2f(x)dx$
###積率母関数 M(t)
$M(t)=E[e^{tX}]=\int_{-\infty}^{\infty}e^{tx}f(x)dx$
##積率母関数の使い道
###積率母関数と確率分布は1対1対応
ある確率分布の積率母関数は固有の関数であり別の確率分布と被ることはない、逆にある積率母関数に対応する確率分布も固有の密度関数であり、両者の1対1対応が成り立つ。
そこで、例えば$X$~$f(x)$,$Y$~$g(y)$のときX+Yが従う確率分布を求めたいとき、直接2変数の変数変換を用いてその分布を求める方法があるが一般的にその計算は複雑となる。
そこでそれぞれの積率母関数について考えてみるとX+Yの積率母関数は
$$M_{X+Y}(t)=E[e^{(X+Y)t}]=E[e^{Xt}]E[e^{Yt}]=M_X(t)M_Y(t)$$
より求まる。そうして求まったX+Yの積率母関数に1対1対応する確率分布が求めたい確率分布となる。この方法は容易に計算できる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392111/fedc2c3a-6ad5-f683-b1f4-626a70d5068c.png)



###積率母関数を用いた期待値・分散の導出

$M'(t)=E[Xe^{tX}], M''(t)=E[X^2e^{tX}]$より
$$M'(0)=E[X], M''(0)=E[X^2] \tag{1}$$
という関係が成り立ちます。さらに
$$V[X]=E[(X-E[X])^2]=E[X^2-2XE[X]+E[X^2]]=E[X^2]-2E[X]^2+E[X]^2=E[X^2]-E[X]^2 \tag{2}$$
となるので
$$E[X] = M'(0) \tag{3}$$
$$V[X] = M''(0)-M'(0)^2 \tag{4}$$
となります。

#連続確率分布一覧と平均・分散・積率母関数
| 分布名| f(x)| xの値域| E[x] | V[x] | M(t) |
|:-:|:-:|:-:|:-:|:-:|:-:|
| 正規分布$N(\mu,\sigma^2)$  |$\sqrt{2\pi \sigma^2}e^{-\frac{(x-\mu)^2}{2\sigma^2}}$   |$[-\infty, \infty]$|$\mu$   | $\sigma^2$  | $e^{\mu t + \frac{\sigma^2 t^2}{2}}$  |
|  指数分布$Exp(\lambda)=Ga(1,\frac{1}{\lambda})$ |$\lambda e^{-\lambda x}$   |$[0, \infty]$| $\frac{1}{\lambda}$  | $\frac{1}{\lambda^2}$  |$(1-\lambda t)^{-1}$   |
|  連続一様分布$U(a,b)$ |$\frac{1}{b-a}$   |$[a,b]$| $\frac{a+b}{2}$  |$\frac{(b-a)^2}{12}$   |  $\frac{e^{bt}-e^{at}}{(b-a)t}$ |
|  ベータ分布$Be(\alpha,\beta)$ |$\frac{1}{B(\alpha,\beta)}x^{\alpha-1}(1-x)^{\beta-1}$   |$[0,1]$| $\frac{a+b}{2}$  |$\frac{(b-a)^2}{12}$   |  省略|
|  ガンマ分布$Ga(s,\frac{1}{r})$ |$\frac{r^s}{\Gamma(s)}x^{s-1}e^{-rx}$   |$[0,\infty]$| $\frac{s}{r}$  |$\frac{s}{r^2}$   |  $(1-\frac{1}{r}t)^{-s}$ |
|  カイ二乗分布$\chi^2(n)=Ga(2,\frac{n}{2})$ |$\frac{2^{\frac{n}{2}}}{\Gamma(\frac{n}{2})}x^{\frac{n}{2}-1}e^{-\frac{x}{2}}$   |$[0,\infty]$| $n$  |$2n$|  $(1-\frac{2}{n}t)^{-2}$ |
|  t分布$t(n)$ |$\frac{\Gamma(\frac{n+1}{2})}{\sqrt{\pi n}\Gamma(\frac{n}{2})}(1+\frac{x^2}{n})^{-\frac{n+1}{2}}  $ |$[-\infty,\infty]$| $0$  |$\frac{n}{n-2}$|  省略|
|  F分布$F(n_1,n_2)$ |$\frac{n_1^{\frac{n_1}{2}}n_2^{\frac{n_2}{2}}}{B(\frac{n_1}{2},\frac{n_2}{2})}\frac{x^{\frac{n_1}{2}-1}}{(n_2+n_1 x)^{\frac{n_1+n_2}{2}}}$   |$[0,\infty]$| $\frac{n_2}{n_2-2}$ |$2{(\frac{n_2}{n_2-2})}^2\frac{n_1+n_2-2}{n_1(n_2-4)}$|  省略 |
[Vue.js]スマブラの確定コンボ表示ツールを作ってみる

環境
Vue.js
vuetify
github.io

スマブラにはどこの％までコンボが確定するかという情報がとても大切です。
しかし、１００キャラくらいあるのにそれを覚えるのは至難の業です。
さらに自分の％によっても確定するかが変わってきます。

それをわかりやすく対戦前、もしくは対戦後に確認するためのツールを作成しました。

データがドンキーのリフコン空上コンボのものしかなかったのでコチラを参考にして作成しております。

・工夫点

![スクリーンショット 2022-01-10 23.11.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706631/98da64b9-5916-bd4c-7ed9-9c93266fc889.png)

まず相手キャラを入力してもらおうと思いましたが、打ち間違いやひらがな、カタカナなど完全一致しないと出ないので利便性に欠けると判断。

次にキャラをドロップダウンで選択にしようとしたが、１００キャラ近くあるものをドロップダウンは探すのが大変。

やり方を検索していると、

        <!-- 入力部分 -->
        相手キャラ→<input class="word" type="text" list="item" placeholder="相手キャラを入力"/>

        <!-- リスト部分 -->
        <datalist id="item">
          <option value="マリオ"></option>
          <option value="ドンキーコング"></option>
          <option value="リンク"></option>
        </datalist>

このようにdatalistというものを使えば入力とドロップダウン両方できると判明。これは便利。

とりあえずitemという配列にキャラ名や％をdataとして入れて、マリオが入力されたら、マリオの確定％が表示されるということにしたい。

ここの表示をどうしようか迷いましたが、泥臭くif文で全て条件分岐させました。
そしたらコードがかなり冗長化しました。綺麗なコードが描けるようになりたい！

```
templete部分
          {{ value }}の場合、
            <p class="my-3">
            ・ジャンプ投げ空上は <span class="text-h4 font-weight-black">{{ jump() }}</span>、
            </p>

script部分

methods: {
    jump:function(){
      console.log(this.value);
      if(this.value == "マリオ"){
        return "~65%"
      } else if(this.value == "ドンキーコング"){
        return "~84%"
      } else if(this.value == "リンク"){
        return "~72%"
      } 続く
```
ひたすらにこれを書いていくだけです。

そして完成したアプリ

https://smash-62fe6.web.app/

![スクリーンショット 2022-02-02 19.27.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706631/f58d6aed-d7fc-c0a7-4b8c-64e1781559c1.png)

ホスティングとOGP設定はFirebaseを使いました。
v9になっており、コードの書き方がだいぶ変わっていたので少し苦労しました。

ユーザー入力の少ないアプリを作ろうとすると結構簡単な実装ですぐ作れるのでいいですね。
これからもどんどん単純な機能だけど、需要のありそうなアプリを作っていきたい

Twitterもやっているのでよかったらフォローお願いします。
https://twitter.com/js_manabitai
万病辞書のMecab用辞書は品詞やコストの設定が怪しい

# 誰が書いてる？
趣味で医療単語の解析をしているもの

# 誰向けの記事？
日本語医療単語の解析で多く使われるMecabに少し詳しい人
ユーザ辞書に万病辞書をつかっている人
よくわからないけどQiitaの記事とかみながら必死に導入を頑張っている初心者

# 問題点？
万病辞書のMecab用辞書がダウンロードできるんだけど、いくつか怪しい点がある。
http://sociocom.jp/~data/2018-manbyo/index.html#howto

## 怪しい点1つ目
品詞がすべて　名詞, サ変名詞 になっているかもしれない
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268777/44ec8f9c-da07-48da-bc73-3f831401ac2a.png)

## 怪しい点2つ目
コストがすべて-10000になっているかもしれない
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268777/cb4ce45b-532d-5135-47b3-7de1691151d1.png)

## 怪しい点3つ目
原型が来るべき場所に「しゅつげんけい;icd=ICD10コード;lv=信頼度LEVEL/freq=頻度LEVEL;標準病名」が来ているかもしれない

## 怪しい点4つ目
読みがカタカナではなくひらがなになっているかもしれない

:::note info
特に3点目のために、既存の辞書やNeoloｇｄ、ComeJisyoとの整合性がとれていない。
:::

:::note info
dicファイルじゃないエクセルファイルがダウンロードできるけど、コンパイル直前のものではないので、上に述べた「すべて」はすべてじゃないかもしれない。
:::
VSCode で開いているファイル間を移動するショートカット

ブラウザでいうところのタブ切り替えのような動きをしたい。
macOS Chrome の　`cmd + shift + [` や `cmd + shift + ]` で前後を行き来するイメージの操作。

赤枠部分。
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19223/1edfb38a-3029-20ad-e3d6-996e4113f46d.png)


## 結論

表示しているエディター間を移動するショートカット

macOS: `ctrl + tab`, `ctrl + shift + tab`
Windows: `ctrl + tab`, `ctrl + shift + tab`
100日でSQLの達人になる@LeetCode! Day1 <left join...on...>

#Qiitaで初めての記事
なるほど**Markdown記法**で記載するんですね！

[ドットインストールの256times](https://256times.com/)で、[LeetCode](https://leetcode.com/)が紹介されていたので、この機会にSQLを勉強しようと思いはじめます。

##175. Combine Two Tables （問題レベル: Easy）
初日の問題はこれ。
２つのテーブルを、キー変数で連結して新しいテーブルを作成するというもの。

最初のテーブルにしかデータが無い場合は、nullを返すような指示があった。
はじめ、これを無視して、```where a.key=b.key```とやってしまい（inner joinになってしまう）間違えた。

ポイントは```left join ... on ...```を使うだった。

[LeetCode](https://leetcode.com/)の問題は、MS SQL Serverで解いています。

Qiitaの投稿でもつまずいた。tagを指定しないと投稿できないのか。
flaskを本番環境で起動する uWSGI + Nginx 

#はじめに
Flaskは簡単にテストサーバーで起動することができますが、これをNginxとuWSGIで本番起動するためのメモです。
下記サイトを参考にしていますので、詳細はこちらをご覧ください。
https://serip39.hatenablog.com/entry/2020/07/06/070000
また、ラズベリーパイで作成していますのでユーザー名がpiになっています。

#uWSGIとFlaskをインストールして仮想環境を立ち上げ

pipをアップグレードしてpipenvをインストール

```
$ pip3 install --upgrade pip 
$ pip3 install pipenv
$ export PATH=$PATH:/home/pi/.local/bin 
```
ディレクトリを作成し、uWSGI・Flaskをインストール後、仮想環境を実行

```
$ mkdir ./flask-test
$ cd ./flask-test
$ pipenv install --python 3.9.2 #任意のバージョンで

$ pipenv install uwsgi    # 仮想環境にuwsgiをインストール
$ pipenv install flask    # 仮想環境にflaskをインストール
$ pipenv shell #仮想環境を実行
```

#uWSGI起動テスト

ディレクトリ直下にtest.pyを作成

```pi/flask-test/test.py
def application(env, start_response):
    start_response('200 OK', [('Content-Type','text/html')])
    return [b"Hello uWSGI"]
```
uWSGIを起動

```
(flask-test)pi$ uwsgi --http :8000 --wsgi-file test.py
```

http://127.0.0.1:8000 にアクセスして「Hello uWSGI」が表示されていればOK

#Flask起動テスト
ディレクトリ直下にapp.pyを作成

```pi/flask-test/app.py
from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello Flask"

if __name__ == "__main__":
    app.run()
```

Flaskテストサーバーを起動

```
(flask-test)pi$ python app.py
```

#uWSGIを通してFlaskを表示

```
(flask-test)pi$ uwsgi --http :8000 --wsgi-file app.py --callable app
```

http://127.0.0.1:8000　にアクセスして 「Hello Flask」が表示されればOK

#Nginx起動テスト
一旦仮想環境からログアウトしてNginxをインストール

```
(flask-test) pi $exit    #exitして仮想環境から出る
pi$ sudo apt install nginx
pi$ sudo /etc/init.d/nginx start 
[ ok ] Starting nginx (via systemctl): nginx.service.
```
http://127.0.0.1:80にアクセスして 「Welcome to nginx!」が表示されていればOK
ローカル内の他のPCからのアクセスしてみても良い
※その場合はhttp://（ここにラズパイのIPアドレス）でアクセス

#Nginxのバーチャルホスト設定を行う

```
$ cd /etc/nginx/sites-available
$ sudo cp default ./app_nginx.conf
```
/var/www/に80番ポートでアクセスしたときに、uWSGIに接続するように設定

```app_nginx.conf
# Virtual Host configuration for example.com
server {
        listen 80;
        server_name example.com;        
        location / {
          include uwsgi_params;
          uwsgi_pass unix:/tmp/uwsgi.sock;
        }
}
```
#uWSGI設定ファイル作成

```
flask-test
 |- app.py
 |- test.py
 |- myapp.ini
```
myapp.iniディレクトリ直下に作成

```myapp.ini
[uwsgi]
module = app
callable = app
master = true
processes = 1
socket = /tmp/uwsgi.sock
chmod-socket = 666
vacuum = true
die-on-term = true
chdir = /home/pi/flask-test
```
※/home/pi/flask-testは任意のディレクトリを指定

#NginxとuWSGIを接続して、動作確認

uWSGIサーバー立ち上げ

```
$ uwsgi --ini myapp.ini
```

別のターミナルでNginxを起動

```
$ sudo /etc/init.d/nginx start
```

http://127.0.0.1:80 にアクセスして「Hello Flask」と表示されれば完成！

#自動起動設定
/etc/systemd/system/内にuwsgi.serviceファイルを作成

```/etc/systemd/system/uwsgi.service
[Unit]
Description = uWSGI
After = syslog.target
[Service]
ExecStart = [uwsgiのパス] --ini /home/pi/flask-test/myapp.ini
Restart=always
KillSignal=SIGQUIT
Type=notify
StandardError=syslog
NotifyAccess=all
[Install]
WantedBy=multi-user.target
```

※[uwsgiのパス]は仮想環境上で $ which flask と入力して表示されるパスを入れてください

下記を実行して自動起動設定

```
systemctl enable uwsgi
```

#コマンドメモ
systemctlのコマンド

```
$ systemctl status uwsgi //status確認
$ systemctl start uwsgi // uWSGI開始
$ systemctl stop uwsgi // uWSGI停止
```
短時間大量リクエスト想定の開発に対応した際のTips

テレビ通販、ECのAPIで短時間大量リクエスト・リアルタイム集計が必要だった際に
パフォーマンスを上げるため実装面で行った内容など

## 概要
* 単純な在庫数などを持っておらず、  
  ある納期までの販売において販売可能な数量（やセット品≒材料の概念）など  
  多少複雑な条件で都度INSERT、都度集計するやや複雑な仕組みの引当
* 基本的には量を捌くため単発の速度を上げる、1リクエストを高速に捌けるよう行った内容  
  （結果としては更新系で0.05秒未満）
* 処理の各所にタイマーを仕掛けてボトルネックを調査、処理を調整していきました
* 環境：Webサーバーx2、DB1
* ネックになるのはDB
* Webサーバーも対策する

## DB関連

### 同時実行数の制限
DBの高負荷対策。
参照系でも無制限にDBに同時処理させると速度低下を起こすため
同時に処理できる最大数を設ける
（速度があげるというより下がらないよう高負荷状態を回避しつつ処理速度が高い状態を保つ）

処理開始時にロックを取り、
ロックを取れなかった場合は0.5秒待機ののちリトライ（上限あり）
（どの程度まで同時処理できるかは負荷テストで要確認。
　8コアでも8件、16コアでも9件程度を超えると遅かったため7件までで制限）

### インデックスの再考

INSERT速度の低下も一応注意

#### **Covering Index** の検討
結果にあたる情報までインデックスに組み込み、SQLがインデックスで完結できるようにする
かなり早い

### 実装の再考

* SQLの発行数そのものを抑える（オーバーヘッドが大きい）

* 一時テーブルの活用はありだが、
  あまり綺麗ではないものの必要なデータをSQLで組み立ててしまい、
  文字列の形でSQL内に直接突っ込むなどしてしまったほうがさらに高速

　多少多くなっても全てをパラメータとして渡してしまうのが
　SQL発行数もDB側の演算も抑えられて結果かなり速い
　0.1秒が大きい差になる場面ではかなり違う。

* 非同期での処理の分離を検討する
  ログ出力など直接必要ない＆リアルタイムである必要もない、
  最悪落ちても良いような処理などは投げっぱなしで結果も待たない、など。

* 遅い構文を避ける
  * DBを使わずPHP側で行える処理はPHP側で行う

* ORDERの代わりに自己結合などに置き換える

* その他普通に計測と修正を繰り返す


## Webサーバー関連

#### Apacheプロセス数の監視
処理が追いつかなかった場合を想定した、Webサーバーの高負荷対策。

Apacheのプロセスが貯まりすぎると固まり、
PHPの処理自体にも到達しなくなりNGレスポンスすら返せなくなる。

これを回避するため処理開始時点のプロセス数を確認し
閾値を超えている場合は処理することなく即レスポンスを返す
（閾値も負荷をかけて待機状態でも問題なく返せている件数を基に
　瞬間的な大量リクエストの懸念があるため、かなり余裕を見て設定）


## その他
### データ量について
集計対象の件数増加はそれなりに速度に影響する（じわじわ遅くなる）
データ総数自体は1億レコード程度あってもほぼ影響はなかった

### スペックアップについて
どちらかというと高負荷になったとき、どれくらい粘れるかはそこそこ変わります
（リクエストが減るまで堪え切れれば復帰できる可能性が高まる）
ただ先のとおりそもそも高負荷を回避する実装をしているため、速度面ではほぼ影響なし。
（明らかにリソースが足りてないなどでなければ、スペックアップしたところで
　元々遅い処理がやたらと速くなるなどということはないと思われる。試した範囲では）
プロセス数や同時実行数の制限を上げられはするけど
大幅に増えるということもなく、積んだところであまり意味はなかった

## さいごに

単発で早くても負荷テストで長時間走らせてみないと気づけないところもあるので
計測して試行錯誤ですね…