短いヘッドフォンケーブルの作成
今使っているヘッドフォンのケーブルが長くて取り回し辛いので、新しく短いヘッドフォンケーブルを作ることにしました。使っているヘッドフォンは AKG K240 Studio なので、ヘッドフォン側がミニXLRジャックのメス、音源デバイス側が3.5mmステレオミニプラグのオスです。
パーツは以下のものを使いました。サウンドハウスで購入し、送料無料で合計 1,095 円です。
- 120円 BELDEN ( ベルデン ) 1503A BLACK
- 480円 AMPHENOL ( アンフェノール ) KS3PC-AU | サウンドハウス
- 495円 REAN ( リアン ) RT3FC-B | サウンドハウス
ミニXLRの方は黒と青のケーブルスリーブが入っています。黒は 2.0mm - 3.0mm 、青は 3.0mm - 4.5mm のケーブルに対応しているようです。今回のケーブルは 3.61mm なので青っぽいんですがゆるかったので黒に変更しました。こちらはピッタリでした。ミニXLRには番号が書いてあり、それぞれ結線は1がアース、2がR、3がLになっています。オヤイデのサイトがわかりやすかったです*1。
次に3.5mmステレオミニプラグの方です。こちらはケーブル側からみて左がL、右がR、残った下の部分にアースを接続します*2。
最後に適当にサウンドチェックをしました。右の音が右から出ていること。左の音が左から出ていることを確認しました*3。
無事ケーブルも短くなって快適になりました。
References
Terraform リソースの命名規則を考える
異論は認めるし、手探りなので特にこれと言った答えはない。
Terraformではリソースに名前をつける(terraform_res_name
の部分)。
resource "aws_s3_bucket" "terraform_res_name" { bucket = "enq-${var.env}-${var.service}-private" acl = "private" tags { Name = "${var.env_tag}-${var.service_tag}-Private" Environment = "${var.env_tag}" ServiceName = "${var.service_tag}" } }
これは、Terraformのコード内で値を参照したいときに利用される。
基本はスネークケースなので、参照するときもスネークケースになるように_
でつなぐ。どんなリソースかは、リソース名に書いてあるので、冗長にならないように付けない。たとえば、サムネイル画像サーバ用のS3バケットだった場合thumbnail_image
とつける(Nameとかはケバブケースでつける)。
resource "aws_s3_bucket" "thumbnail_image" { bucket = "foo-${var.env}-${var.service}-private" acl = "private" tags { Name = "${var.env_tag}-${var.service_tag}-Private" Environment = "${var.env_tag}" ServiceName = "${var.service_tag}" } }
バケットを参照するときは、${aws_s3_bucket.thumbnail_image.bucket}
となる。
環境は、変数から注入してタグなどに埋め込むので気にしなくて良いとおもう。リソース名に変数は使えない issue-#571。とは言えこういう話もある issue-#1976。
Atomで設定でソフトラップを無効にしても折り返されてしまう
ソフトラップとは文字列がエディタの幅を超えた時に表示だけ自動的に改行する機能で、多くの場合は便利なんだけど表とか行を比べて見たいときには非常に見づらくなってしまう。
左: ソフトラップ 右: ソフトラップ無効
Atomでソフトラップを無効にしても折り返されちゃうなーと困っていた。ちゃんと、“Soft Wrap”設定のチェックは外れているし。
と思ったら、ツールバーのメニューからView > Toggle Soft Warp
という項目を選ぶと切り替わった。なぜこんな項目を作ったのか。。。
DockerのコンテナIDとプロセスIDを調べる
先日、とあるプロセスが暴走していてCPUを食いまくっていた。バッチサーバで各バッチをコンテナで実行しているんだけど、どのプロセスがどのコンテナかを調査する時にPIDからコンテナID(もしくはコンテナIDからPID)を調べる必要がある。 /proc/$PID/cgroup
を見るとPIDからコンテナIDがわかるらしいという記事があったので、その方法で調べたりしていたがいちいちコンテナIDを調べて紐付けるのが面倒くさかった。
何かいい方法はないかなと思って、Docker Inspectを見ていると、PID
があった。あとはフォーマットでいい感じに表示すれば使えそう。
最終的には次のようなコマンドになった。
docker ps -q | xargs docker inspect -f '{{.Config.Hostname}} {{.Config.Image}} {{.Args}} {{.State.Pid}} {{range $p, $conf := .NetworkSettings.Ports}} {{$p}}->{{(index $conf 0).HostPort}} {{end}}'
出力結果はこんなかんじになる。それぞれ、コンテナID、イメージ名、引数、プロセスID、バインドしているポート。という感じにした。
a10b8169f7e1 batch-cli [batch-job-foo] 109442 284d23a4e10b batch-cli [batch-job-bar] 102905 8edc6b9ce586 nginx:1.13.3-alpine [-g daemon off;] 3409 80/tcp->80
docker ps -q
でコンテナIDだけ出力して、xargs
でdocker inspect
に渡す。あとはフォーマットで整えるんだけど、コンテナIDはId
というフィールドでいるんだけど、長いのでConfig.Hostname
を使った。もしかしたらホスト名を指定しているコンテナの場合はホスト名が表示されると思うので、そういう場合はId
を使うしかない。本当はヘッダも表示してあげたいし、タブ区切りか表形式にしてあげたいんだけど、\t
もtable
も効かないので諦めた。
IntelliJ でターミナルを使う
たまに使ってたんだけど、なぜかウィンドウ下に出てこなくなってた。
Cmd+,
で Preferencesを開いてプラグインのインストール画面でTerminalにチェックを入れると表示される。
IntelliJの再起動をすると。
表示された。
Alt+F12
でフォーカスを当てられるというが、npmのペインに飛んでしまって不安定。
Shift+Cmd+F11
に割り当てた。
お名前.comでIPとドメインを紐づける
書くほどのことではないんだけど、ちょっと運用を引き渡すことも兼ねてのメモ。
まず、お名前.comでドメインを取得する。
例えば、 example.com
。
Aレコードとして、ドメインと該当サーバのIPを紐づける。
例えば、 192.0.2.1
。サブドメイン入力欄は空で、IPアドレスだけ入力する。
.example.com => 192.0.2.1
CNAMEを追加する。 サブドメインにワイルドカード、ドメインを2で指定したドメイン。
こうすると、 www.example.com
でも foo.example.com
でも example.com
に送ってもらえる。
*.example.com => example.com
DNSレコード設定用ネームサーバー変更確認にチェックを入れる。 一番最初はチェック入っているはずだけど、一応。
References
Jenkins を プラグインと同時にアップグレードしたらエラーが出て起動しなくなった
「Jenkins新しいのあるよ」という通知が出てたので、いつものようにアップグレードしたらエラーが出て起動しなくなった。おそらく、問題はプラグインアップデート中だったということ。。
java.lang.AbstractMethodError
java.lang.AbstractMethodError: org.jenkinsci.plugins.pipeline.modeldefinition.validator.ModelValidatorImpl.validateElement(Lorg/jenkinsci/plugins/pipeline/modeldefinition/ast/ModelASTValue;)Z at org.jenkinsci.plugins.pipeline.modeldefinition.ast.ModelASTValue.validate(ModelASTValue.java:74) at org.jenkinsci.plugins.pipeline.modeldefinition.ast.ModelASTNamedArgumentList.validate(ModelASTNamedArgumentList.java:70) at org.jenkinsci.plugins.pipeline.modeldefinition.ast.ModelASTStep.validate(ModelASTStep.java:72) at org.jenkinsci.plugins.pipeline.modeldefinition.ast.ModelASTTreeStep.validate(ModelASTTreeStep.java:35) at org.jenkinsci.plugins.pipeline.modeldefinition.ast.ModelASTBranch.validate(ModelASTBranch.java:40) at org.jenkinsci.plugins.pipeline.modeldefinition.ast.ModelASTStage.validate(ModelASTStage.java:90) at org.jenkinsci.plugins.pipeline.modeldefinition.ast.ModelASTStages.validate(ModelASTStages.java:43) at org.jenkinsci.plugins.pipeline.modeldefinition.ast.ModelASTStages.validate(ModelASTStages.java:37) at org.jenkinsci.plugins.pipeline.modeldefinition.ast.ModelASTPipelineDef.validate(ModelASTPipelineDef.java:66) at org.jenkinsci.plugins.pipeline.modeldefinition.ast.ModelASTPipelineDef$validate.call(Unknown Source) at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:113) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:125) at org.jenkinsci.plugins.pipeline.modeldefinition.parser.ModelParser.parsePipelineStep(ModelParser.groovy:251) at org.jenkinsci.plugins.pipeline.modeldefinition.parser.ModelParser.this$2$parsePipelineStep(ModelParser.groovy) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite$PogoCachedMethodSiteNoUnwrapNoCoerce.invoke(PogoMetaMethodSite.java:210) at org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite.callCurrent(PogoMetaMethodSite.java:59) at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:52) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:154) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:174) at org.jenkinsci.plugins.pipeline.modeldefinition.parser.ModelParser.parse(ModelParser.groovy:149) at org.jenkinsci.plugins.pipeline.modeldefinition.parser.ModelParser$parse.callCurrent(Unknown Source) at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:52) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:154) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:174) at org.jenkinsci.plugins.pipeline.modeldefinition.parser.ModelParser.parse(ModelParser.groovy:116) at org.jenkinsci.plugins.pipeline.modeldefinition.parser.ModelParser.parse(ModelParser.groovy) at org.jenkinsci.plugins.pipeline.modeldefinition.parser.GroovyShellDecoratorImpl$1.call(GroovyShellDecoratorImpl.java:78) at org.codehaus.groovy.control.CompilationUnit.applyToPrimaryClassNodes(CompilationUnit.java:1065) at org.codehaus.groovy.control.CompilationUnit.doPhaseOperation(CompilationUnit.java:603) at org.codehaus.groovy.control.CompilationUnit.processPhaseOperations(CompilationUnit.java:581) at org.codehaus.groovy.control.CompilationUnit.compile(CompilationUnit.java:558) at groovy.lang.GroovyClassLoader.doParseClass(GroovyClassLoader.java:298) at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:268) at groovy.lang.GroovyShell.parseClass(GroovyShell.java:688) at groovy.lang.GroovyShell.parse(GroovyShell.java:700) at org.jenkinsci.plugins.workflow.cps.CpsGroovyShell.doParse(CpsGroovyShell.java:129) at org.jenkinsci.plugins.workflow.cps.CpsGroovyShell.reparse(CpsGroovyShell.java:123) at org.jenkinsci.plugins.workflow.cps.CpsFlowExecution.parseScript(CpsFlowExecution.java:517) at org.jenkinsci.plugins.workflow.cps.CpsFlowExecution.loadProgramAsync(CpsFlowExecution.java:614) at org.jenkinsci.plugins.workflow.cps.CpsFlowExecution.onLoad(CpsFlowExecution.java:589) at org.jenkinsci.plugins.workflow.job.WorkflowRun.onLoad(WorkflowRun.java:613) at hudson.model.RunMap.retrieve(RunMap.java:225) at hudson.model.RunMap.retrieve(RunMap.java:57) at jenkins.model.lazy.AbstractLazyLoadRunMap.load(AbstractLazyLoadRunMap.java:500) at jenkins.model.lazy.AbstractLazyLoadRunMap.load(AbstractLazyLoadRunMap.java:482) at jenkins.model.lazy.AbstractLazyLoadRunMap.getByNumber(AbstractLazyLoadRunMap.java:380) at hudson.model.RunMap.getById(RunMap.java:205) at org.jenkinsci.plugins.workflow.job.WorkflowRun$Owner.run(WorkflowRun.java:891) at org.jenkinsci.plugins.workflow.job.WorkflowRun$Owner.get(WorkflowRun.java:901) at org.jenkinsci.plugins.workflow.flow.FlowExecutionList$1.computeNext(FlowExecutionList.java:65) at org.jenkinsci.plugins.workflow.flow.FlowExecutionList$1.computeNext(FlowExecutionList.java:57) at com.google.common.collect.AbstractIterator.tryToComputeNext(AbstractIterator.java:143) at com.google.common.collect.AbstractIterator.hasNext(AbstractIterator.java:138) at org.jenkinsci.plugins.workflow.flow.FlowExecutionList$ItemListenerImpl.onLoaded(FlowExecutionList.java:178) at jenkins.model.Jenkins.<init>(Jenkins.java:999) at hudson.model.Hudson.<init>(Hudson.java:86) at hudson.model.Hudson.<init>(Hudson.java:82) at hudson.WebAppMain$3.run(WebAppMain.java:235) Caused: hudson.util.HudsonFailedToLoad at hudson.WebAppMain$3.run(WebAppMain.java:249)
一行目の org.jenkinsci.plugins.pipeline.modeldefinition
を検索してみると、 jenkinsci/pipeline-model-definition-plugin がヒットした。
このプラグインをロールバックしてみよう。
Jenkins Home 下の plugins
ディレクトリに入っているので覗いてみる(今回の環境は /var/lib/jenkins/plugins
)。
3つのファイルが見つかった。
pipeline-model-definition pipeline-model-definition.bak pipeline-model-definition.jpi
bak
というのがバックアップファイルなので、こいつをリネームする。
mv pipeline-model-definition.jpi pipeline-model-definition.jpi.latest mv pipeline-model-definition.bak pipeline-model-definition.jpi mv pipeline-model-definition pipeline-model-definition.latest unzip pipeline-model-definition.jpi -d pipeline-model-definition
そして、Jenkins再起動したが、、、直らない。。。 おそらく依存プラグインもすべて戻さなくてはだめなのだろう。 更新された以下のプラグインたちをスクリプトを書いて全部巻き戻すことに。
# find . -maxdepth 1 -mtime -1 | grep '.jpi' ./pipeline-graph-analysis.jpi ./blueocean-commons.jpi ./blueocean-rest.jpi ./blueocean-pipeline-scm-api.jpi ./pipeline-model-api.jpi ./pipeline-model-extensions.jpi ./blueocean-web.jpi ./docker-commons.jpi ./blueocean-jwt.jpi ./pipeline-stage-tags-metadata.jpi ./pipeline-build-step.jpi ./timestamper.jpi ./cloudbees-bitbucket-branch-source.jpi ./blueocean-jira.jpi
うまく起動した。
一部の古い Safari で CORS 違反の403エラーが出る
次のような環境で CORS 違反のエラーが出てしまう症状に会った。 Chrome や Firefox では全く問題はなかったんだけど。
iOS 9.0, 9.2, 9.3.5, 10.2 MacOS 10.10.4(Yosemite)/Safari 8.0.7
「Go言語(Go-Json-Rest)のCORSでのハマり。Safariだけでハマった話。 - Qiita」 で書かれているように、
Access-Control-Allow-Headers
に Origin
を追加したら見れるようになった。
Amazon.com とアカウント結合している場合は Amazon Dash Button が使えない
前回のサイバーマンデー(日本)で Amazon Dash Button が半額の250円だったので幾つか購入した。しかし、WiFi設定を行っても「接続に失敗しました」というメッセージが表示され、接続されない。
ヘルプに書いて有ることを片っ端から試してみた。iPhoneの再起動、AmazonAppの再インストール、Wi-Fiルータの再起動、Wi-Fiルータを交換、別のDashボタンなど。
最終的にはサポートに電話し、同じことを一緒に繰り返してみたが接続できず。調査してもらうことになった。しばらくすると、返答が来た。結果は「Amazon.comとアカウント結合している場合は、現状設定できない」ということだった。修正中らしいが、いつ修正されるかは未定とのこと。できることは、「別のアカウントで使う」、「返品、「待つ」のどれかになるそうだ。せっかく半額で買ったのに返品するのはちょっと悲しいし、ボタンガジェットとして使えるのでとりあえず別アカウントで消費しようかなというところに落ち着いた。
Amazon.comとアカウント結合してよかったこと一度もないなぁ。
ちなみに、「Amazon.comのアカウントを結合済みのお客様」にはこのように書かれている。
アカウント結合に関する注意点
注:アカウント結合のお申し込みは受け付けておりません。
- 結合済みのアカウントは、結合を解除することはできません
- Amazon.co.jpでご利用いただけないタイプのコンテンツ(定期刊行物、オーディオブック商品など)は結合後に利用できなくなっています
- Amazon.co.jpで販売されていないKindle端末、Fireタブレット、およびKindle無料アプリ(Kindle for iOSなど)での日本語コンテンツの利用はできません
- Amazon.comとAmazon.co.jpのいずれかでプライム会員になっているお客様はアカウント結合後、Amazon.co.jpのオーナーライブラリーはご利用いただけません
- アカウント結合したお客様がAmazon.co.jpのKindle Unlimitedに登録する場合は、日本のアカウントでAmazon.co.jpにサインインをする必要があります。また、購入先サイトおよび居住国設定を日本に設定してください。なお、Kindle Unlimitedで利用中の本の確認および管理、Kindle Unlimitedの会員情報の管理およびキャンセルは、 https://www.amazon.co.jp/gp/kindle/ku/ku_central にアクセスしてください。アカウント結合したお客様のコンテンツと端末の管理ページには表示されませんのであらかじめご了承ください。
References
direnv で AWSのIAMアカウントを自動的に切り替える
複数のAWS IAMアカウント(例えば会社用と自分用)を使っているときに、間違って他のアカウントを使わないようにしたい。「--profile
」オプションで環境を切り替える事もできるが、付け忘れたりするリスクがある。
試行錯誤・運用した結果、ホームディレクトリに「~/.aws/credentials
」を置かず、 direnv
を使って環境変数で設定する方法に落ち着いた。
direnvのインストール方法とかはこちらを参考に。
.envrcをどこに配置するか
大抵、該当のリポジトリに移動してから作業を行うため、リポジトリに配置してもよい。しかし、複数のリポジトリで同じAWSアカウントを使いたいこともあるので、Organizationのレベルに .envrc
を配置している。
~ ├── github.com │ ├── .envrc │ ├── bar │ └── foo └── github.office.example.com ├── .envrc ├── baz └── qux
.envrc
の中身については以下の通り。
export AWS_ACCESS_KEY_ID='xxxxxxxxxxxxx' export AWS_SECRET_ACCESS_KEY='xxxxxxxxxxxxxxx'
必要であれば source_env ..
などで上の階層を読み込むとよい。
References
GitHub Pages にホスティングしたTypeDocが404になる
typedocで 「no such file or directory, stat」 というエラーが出る
typedoc
でドキュメントを生成しようとすると以下のようなエラーが出るようになってしまった。
❯ yarn docs yarn docs v0.27.5 $ typedoc --theme default --out docs src Using TypeScript 2.4.1 from /Users/yoshiki_aoki/work/src/.../node_modules/typescript/libfs.js:954 binding.stat(pathModule._makeLong(path)); ^ Error: ENOENT: no such file or directory, stat '/Users/yoshiki_aoki/work/src/.../node_modules/typedoc-default-themes/bin/default/assets'
試しに全部node_module
消してキャッシュもクリーンしてインストールし直してみた。
yarn cache clean yarn
yarn
でインストールし直してもダメ。lsで該当ファイルを確認してもない。。。
ふと、npm
を使ってインストールしてみた。
npm install
したら治った。
🤔
.yarnclean が原因
2017/09/14 追記。
色々と調べてみるとどうやら、yarn clean
を実行して作成された.yarnclean
が原因っぽいということがわかった。
確かに中を覗いてみると、assets
という項目がある。これによって、node_modules
内のassets
が削除されている模様。
そして、最新バージョン(1.0.0以降)ではyarn clean
からyarn autoclean
になっている。
ローカルで使っていたバージョンが v0.27.5
なのでアップグレードした。
Alfred 3のForce Keyboardがうまく動かない(Sierra)
Alfredの設定で、 Preference > Advanced > Force Keyboard
を設定しておくと、IMEが日本語入力になっていても自動的に英字入力に切り替えてくれる。
しかし、TouchBarのMacBookProになってからこの機能がうまく動かない。調べるとSierraになってから発生してるらしい。
事象としては一文字めだけ日本語入力になってしまう。例えば、「chrome」と入力しようとすると「chrome」のようになったり、「alfred」と入力しようとすると「あlfred」となる。
調べてみたらAlfred公式のフォーラムにポストがあった。
Force keyboard is a little buggy on Sierra - Noted - Alfred App Community Forum
こんな書き込みが。
I’ll look into this, but for now, try switching Alfred to focus compatibility mode in the Appearance > Options preferences
ほー。なるほど🤔。
目立たないところにあるなぁ。。
Focusingを Compatibility Mode
に変更。
今の所、いい感じで動いてる。
MySQLで外部キー制約を課すべきか
外部キー制約を設けておくと、存在しないエンティティ(例えばユーザIDなど)にリレーションをシステム的に持てないようにすることができる。これは、データの不整合を防ぐためには非常に有効な手段であると思う。
SQLアンチパターンには「キーレスエントリ(外部キー嫌い)」というタイトルで章が割かれている。基本的に制約を設けた方が色々とよいという結論である模様。
私が所属しているチームでも同様な議論がされたため、論点になった箇所と考察を書いておく(普段はこういうところにあんまり書かないんだけど、なんか勿体無いなと思って)。最終的な結論としては、外部キー制約は設けない方向で行くという決断をした。
外部キー制約に関する論点
データの整合性
外部キー制約を設けると、最初にも書いたが存在しない要素のIDにはリレーションを持つことができないため、データの生合成を保証することができる。これは、制約を設けることの大きなメリット。人間が考えることを少なくし、システムを強固にしてくれる。
テストについて
テストデータを作成する場合、リレーション元のテーブルから順番に作成しないといけないため、テストデータに工夫が必要でコストが高いという意見が出た。この件に関しては、どちらのケースでも対応する方法はある模様。
少し乱暴だがテストデータをインサートする前に SET FOREIGN_KEY_CHECKS=0;
を発行して外部キー制約のチェックを無効にする方法がある。そして、テストが終わった時に SET FOREIGN_KEY_CHECKS=1;
チェックを有効にする。本番のDBではあり得ないが、テストDBなら問題はないだろう(そもそもモックしろとかそういう話も色々あるが)。
しかし、これはこれで「外部キー制約を踏まえたテストができないので全てが丸く収まるというわけにはいかなそう」という問題もでてきた。
テストデータを作成するのが面倒くさいというが、何も制約がない状況下で制約を担保するようなテストを書く方が手間がかかり、考え事が増えるという話もある(テストfixtureを準備するのがめんどくさい(?))。
とはいえ、安全側に倒すことを考えるとこの件に関しても外部キー制約を設けた方が良さそうな気がする。
パーティショニングされたテーブル
InnoDBはパーティショニングされたテーブルについて、外部キー制約をサポートしないという悲しい事実がある。これはどうにもできない。親だろうが、子だろうがどちらか一方がパーティショニングされたテーブルだった場合、制約を課すことができない。 また、シャーディングされたテーブルにおいても同様に、外部キー制約を課すことができない。
InnoDB ストレージエンジンを使用するパーティション化されたテーブルでは、外部キーはサポートされません。これは具体的には、次の 2 つの記述が true であることを意味します。 1. ユーザー定義パーティショニングを使用する InnoDB テーブルの定義には、外部キー参照を含めることはできません。定義に外部キー参照が含まれる InnoDB テーブルはパーティション化できません。 2. InnoDB テーブル定義に、ユーザーパーティション化されたテーブルへの外部キー参照を含めることはできません。ユーザー定義パーティショニングを持つ InnoDB テーブルに、外部キーによって参照されるカラムを含めることはできません。 https://dev.mysql.com/doc/refman/5.6/ja/partitioning-limitations.html
今回のプロジェクトはパーティショニングされたテーブルがそれなりの個数存在し、当初はSpiderでのシャーディングも検討していた。そのため、外部キー制約は全てのテーブルに設けない方向で今まで進んでいた。
導入コスト
パーティションの件があるので、すでに外部キー制約を設けない方向でプロジェクトを進めている。とはいえ、制約を課せるところだけ課そうとする意見もあったが、どちらかに統一したいという意見もあった。それに加え、今から導入するとそれなりの作業が発生し導入コストがかかる(これは外部キー制約のデメリットではないが…)。
暗黙のインデックス
このスライドにも書いてあるが、暗黙のインデックスというのが貼られ、インデックスサイズの肥大化につながる。デメリットだけどあまり気にしない。
意図しない箇所でのテーブルロック
「リレーション先の行にshared lockがかかる」で指摘されていることで、こっちは逆に外部キー制約を課すデメリットなのかもしれない。
リレーションの親テーブルをうっかりshared lockしていることがある http://songmu.github.io/slides/fk-night/#15
削除対象のレコードがある場合
レコードの削除が想定されるテーブルに外部キー制約を課すと、レコード削除が難しくなるケースがありうるという意見。整合性を保つゆえ仕方ないが、何も考えずにカスケード削除を行うと事故を起こす可能性は否定できないとも。これは、整合性を保ってはくれるがDBの構造を理解しない・気にしなくていいというわけではないのではない。これに関しては、構造は理解していることに越したことはないのだが、「親テーブルの行を消したときに子テーブルの行は残したい」という状況が発生した時点で、設計を見直すべきである。
考察
外部キー制約を課すことによって得られるメリットである、「データの整合性」は非常に魅力的ではあるが、パーティショニングをしているテーブルが多いこと、また現在の状況と対応工数を踏まえて検討すると、外部キー制約を課さない方向で行こうと思う。ただ、注意すべきは整合性が取れないということを前提にシステムを設計・構築しなければならない。例えば、どういうことを意味するかというと、APIなどで投稿されるあるキー(例えば商品IDなど)をそのまま信じてDBにインサートしてはいけない(当たり前だけど)。必ずそのIDがデータとして有効なものであるかを確認する必要がある(整合性は取れなかったとしても)。このことを考えると一部だけ外部キー制約を課すという案もある。これは必要に応じで外部キー制約を課したほうが少しはマシになるのではないか。
今回は採用しない方針で進めるが、外部キー制約を課さない方がよいというわけではない。当然だが、全ての事柄はケースバイケースである。外部キー制約を課さないとしても、エンティティ同士のリレーションを表現する図(つまりER図)は人間が見るときに欲しい。
Foreign Key Nightの「我々(主語が大きい)は何故MySQLで外部キーを使わないのか」がだいぶ参考になった。🤔
References
aws-cliの導入とAWS Credentialの設定
AWSが公開してるドキュメントを見ればわかるんだけど、再セットアップ時に行ったので備忘録。
まず、IAMでアクセスキーを発行する。
そして、aws
コマンドのインストール。
brew install awscli
終わったらターミナルで aws configure
を実行して設定を行う。
❯ aws configure AWS Access Key ID [None]: XXXXXXXXXXXXXXXXXXXX AWS Secret Access Key [None]: xxxxxxxxxxxxxxxxxxxxxxxxxxx Default region name [None]: ap-northeast-1 Default output format [None]: json