短いヘッドフォンケーブルの作成

今使っているヘッドフォンのケーブルが長くて取り回し辛いので、新しく短いヘッドフォンケーブルを作ることにしました。使っているヘッドフォンは AKG K240 Studio なので、ヘッドフォン側がミニXLRジャックのメス、音源デバイス側が3.5mmステレオミニプラグのオスです。

パーツは以下のものを使いました。サウンドハウスで購入し、送料無料で合計 1,095 円です。

ミニ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で設定でソフトラップを無効にしても折り返されてしまう

ソフトラップとは文字列がエディタの幅を超えた時に表示だけ自動的に改行する機能で、多くの場合は便利なんだけど表とか行を比べて見たいときには非常に見づらくなってしまう。

左: ソフトラップ 右: ソフトラップ無効

f:id:ringo6119:20180405113359p:plain

Atomでソフトラップを無効にしても折り返されちゃうなーと困っていた。ちゃんと、“Soft Wrap”設定のチェックは外れているし。

f:id:ringo6119:20180405113418p:plain

と思ったら、ツールバーのメニューからView > Toggle Soft Warpという項目を選ぶと切り替わった。なぜこんな項目を作ったのか。。。

f:id:ringo6119:20180405113433j:plain

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だけ出力して、xargsdocker inspectに渡す。あとはフォーマットで整えるんだけど、コンテナIDはIdというフィールドでいるんだけど、長いのでConfig.Hostnameを使った。もしかしたらホスト名を指定しているコンテナの場合はホスト名が表示されると思うので、そういう場合はIdを使うしかない。本当はヘッダも表示してあげたいし、タブ区切りか表形式にしてあげたいんだけど、\ttableも効かないので諦めた。

IntelliJ でターミナルを使う

たまに使ってたんだけど、なぜかウィンドウ下に出てこなくなってた。

f:id:ringo6119:20180317012727j:plain

Cmd+, で Preferencesを開いてプラグインのインストール画面でTerminalにチェックを入れると表示される。

f:id:ringo6119:20180317012741j:plain

IntelliJの再起動をすると。

f:id:ringo6119:20180317012756j:plain

表示された。

Alt+F12 でフォーカスを当てられるというが、npmのペインに飛んでしまって不安定。 Shift+Cmd+F11 に割り当てた。

お名前.comでIPとドメインを紐づける

f:id:ringo6119:20180202164023p:plain

書くほどのことではないんだけど、ちょっと運用を引き渡すことも兼ねてのメモ。

まず、お名前.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

f:id:ringo6119:20180202151132j:plain

DNSレコード設定用ネームサーバー変更確認にチェックを入れる。 一番最初はチェック入っているはずだけど、一応。

f:id:ringo6119:20180202151137j:plain

References

Jenkins を プラグインと同時にアップグレードしたらエラーが出て起動しなくなった

f:id:ringo6119:20180117145415j:plain

「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 違反のエラーが出てしまう症状に会った。 ChromeFirefox では全く問題はなかったんだけど。

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-HeadersOrigin を追加したら見れるようになった。

Amazon.com とアカウント結合している場合は Amazon Dash Button が使えない

f:id:ringo6119:20180110224918j:plain

前回のサイバーマンデー(日本)で 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.comAmazon.co.jpのいずれかでプライム会員になっているお客様はアカウント結合後、Amazon.co.jpのオーナーライブラリーはご利用いただけません
  • アカウント結合したお客様がAmazon.co.jpKindle Unlimitedに登録する場合は、日本のアカウントでAmazon.co.jpにサインインをする必要があります。また、購入先サイトおよび居住国設定を日本に設定してください。なお、Kindle Unlimitedで利用中の本の確認および管理、Kindle Unlimitedの会員情報の管理およびキャンセルは、 https://www.amazon.co.jp/gp/kindle/ku/ku_central にアクセスしてください。アカウント結合したお客様のコンテンツと端末の管理ページには表示されませんのであらかじめご了承ください。

References

direnv で AWSのIAMアカウントを自動的に切り替える

f:id:ringo6119:20180103185413p:plain

複数の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になる

GitHub Pagesは、 _で始まるファイルが表示されない。これはGitHubの仕様である模様。

TypoeDocは module ディレクトリ以下に _ で挟まれたHTMLファイルを生成する。そのまま、GitHub Pagesにホスティングしてしまうと404になってしまう。

これらのファイルを表示するためには、ドキュメントのルートに( docs 下をGitHubPagesで公開していたら docs ディレクトリ直下).nojekyll ファイルを設置すればよい。

touch .nojekyll

References

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が削除されている模様。

qiita.com

そして、最新バージョン(1.0.0以降)ではyarn clean からyarn autocleanになっている。

github.com

ローカルで使っていたバージョンが v0.27.5 なのでアップグレードした。

Alfred 3のForce Keyboardがうまく動かない(Sierra)

Alfredの設定で、 Preference > Advanced > Force Keyboard を設定しておくと、IMEが日本語入力になっていても自動的に英字入力に切り替えてくれる。

f:id:ringo6119:20170902014141j:plain

しかし、TouchBarのMacBookProになってからこの機能がうまく動かない。調べるとSierraになってから発生してるらしい。

事象としては一文字めだけ日本語入力になってしまう。例えば、「chrome」と入力しようとすると「chrome」のようになったり、「alfred」と入力しようとすると「あlfred」となる。

f:id:ringo6119:20170902014136g:plain

調べてみたら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

ほー。なるほど🤔。

f:id:ringo6119:20170902014131j:plain

目立たないところにあるなぁ。。

Focusingを Compatibility Mode に変更。

f:id:ringo6119:20170902014139j:plain

今の所、いい感じで動いてる。

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でアクセスキーを発行する。

f:id:ringo6119:20170822020821p:plain

そして、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

References