DBは1つなのに分散トランザクションでエラーになる問題を解決したお話

最近仕事で参画したPJにおいて、分散トランザクションがうまく処理できていなくてシステムエラーが発生している。何とかしてほしい。と依頼を受けました。
しかもエラーは発生するときと発生しないときがあるそうです。

まだプログラムの中身は全然理解できていない段階だったのですが、全体の構成としてはAPサーバ1台とDBサーバ1台なので分散トランザクションは発生しない、というか不要なはず。
APサーバとDBサーバが分かれているから分散トランザクションが起きるとチームの人は教えてくれましたがイマイチ腑に落ちません。。。
となり色々調べてみた結果を記載します。

分散トランザクションって何なの？という方は以下の記事にまとめていますので参考にしてください。
[分散トランザクションってトランザクションと何が違うの??](https://qiita.com/yShig/items/0168e651d6f3ef105f35)

#1.環境
まず初めにアプリの開発環境や実行環境等を記載します。
勘のいい方ならこの時点で原因が分かるのかなと思います。

* 開発言語：  
C#.NET
* フレームワーク：  
ASP.NET5, .NETFramework4.5
* 開発環境：  
VisualStudio2017
* 実行環境：  
windowsServer2016
* DB:  
SqlServer2016


#2.現象

とりあえずエラーのログを確認して本当に分散トランザクションが原因でエラーが発生しているのか確認しました。

```:ログ
 分散トランザクション マネージャ (MSDTC) のネットワーク アクセスは
無効になっています。 コンポーネント サービス管理ツールを使用して、
MSDTC のセキュリティ構成でネットワーク アクセスの DTC を有効にして
ください。
```

どうやらホントに分散トランザクションが発生してそれをうまく処理できる設定になっていないからエラーになっているようです。

自分の端末で再現するためにエラーを見つけた人に話を聞くといつも必ず発生するわけではなく、複数のテーブルに書き込む処理を行った際に発生することがあるらしい。。。

#3.分散トランザクションが発生する理由

そもそもなぜDB1つなのに分散トランザクションが発生していたのか説明します。
原因としてはトランザクションを張るのにSystem.Transactions.TransactionScopeを使用しているからでした。
TransactionScopeの内部で複数回DDLを実行すると分散トランザクションとなるそうです。
(これって毎回分散トランザクションになるのでしょうか？
それとも何かしら条件があって分散トランザクションにならない場合もあったりするのでしょうか？？調べてもここはわかりませんでした。絵詳しい人いましたらコメントで教えてください。。。)

念のために記述しておきますが、TransactionScopeがダメだという訳ではなく、2019年3月時点でも「.NetFramework トランザクション」で検索したらまずTransactionScopeの話が出てくる、推奨されている方法です。
ただ、落とし穴もあるので注意が必要だよ。という話です。

ちなみに開発環境などでアプリケーションとデータベースが同じサーバ上で動作しているような環境だと分散トランザクションは発生しないようです。
チームの人が言ってたことも間違ってたわけじゃなかったんですね。

#4.とりあえず行った対策

##### MSDTCの設定

こちらサイト様を参考にしてMSDTCとファイアウォールの設定を行いました。
https://blogs.technet.microsoft.com/jpiis/2018/02/05/msdtc-settings/

この設定で発生の頻度は大きく下がりました。
が、まだたまに発生する様子。しかし、エラーメッセージは以下のものに変わりました。
基本トランザクション マネージャーとの通信が失敗しました。 ---> System.Runtime.InteropServices.COMException: MSDTC トランザクション マネージャーは、通

```:ログ
信の問題のため、送信元のトランザクション マネージャーからトランザクションをプルできませんでした。原因として、ファイアウォールが存在していて MSDTC プロセスの例外がないこと、2 台のコンピューターが NetBIOS 名でお互いを識別できないこと、または 2 つのトランザクション マネージャーのいずれかでネットワーク トランザクションのサポートが有効になっていないことが考えられます。 (HRESULT からの例外:0x8004D02B)
```

もう少し対策が必要なようです。

#5. 上記の問題の解決となった対策

問題はログの```2 台のコンピューターが NetBIOS 名でお互いを識別できないこと```
という部分でした。
プログラムの設定ファイルにはDBサーバのIPアドレスを記載していて基本的には通信はできているんだから名前解決には問題ないはず。と思っていたのですが、結局詳しく調べてみるとこの名前解決が問題でした。
通常はアプリケーション⇔SQLServerの通信はTCP/IPというプロトコルを使っています。
TCP/IPではIPアドレスを使用して通信相手特定を行っています。
しかし、分散トランザクションが発生してMSDTCを介して通信を行う際にはプロトコルがNetBeui(ネットビューイ)というものを使用するようになるようです。
このNetBeuiではログに出ていたNetBIOS名を使って通信相手の特定を行います。
図で書くとこんな感じです。

<img width="632" alt="キャプチャ.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/162114/62f5ba7a-0998-45bc-052f-7c7ce9610043.png">

じゃあNetBeuiって何が設定されているのかというと昔は個別に設定していたようですが、
今はホスト名と同じ値が自動で設定されているようです。
つまりIPアドレス⇔ホスト名の変換ができていないことになります。

なぜ変換できていないんだろう？
使用しているAWS環境ではセキュリティグループの設定でpingなどのICMPをデフォルトで弾くようになっているのが関係してDNSサーバを見つけられないとか？？
(ネットワーク周りの知識には疎くてすみません...)

自分の持っている権限ではセキュリティグループの設定変更できなかったので管理部門に連絡して権限つけてもらうという策も考えましたが、時間かかるし他のインスタンスへの影響も一応あるしこれで治る保証もなかったのでとりあえずwindowsのhostsファイルに通信相手のIPアドレスとホスト名の対応を書いてみました。

書き方はコチラのサイト様を参考にしました。
http://onocom.net/blog/windows-hosts-file/
その後、念のためコチラのサイト様を参考にして変更を反映する処理を行いました。
http://poppory.hatenablog.com/entry/2015/03/29/021104

そしてその後しばらく様子を見たところ、分散トランザクションによるエラーは発生しなくなりました。
めでたしめでたし。








と行きたいですが、残った疑問が一つ。
名前解決できていなかったならなぜエラーは発生したりしなかったりしたのでしょう？？
「3.分散トランザクションが発生する理由」で書いたように毎回分散トランザクションになってしまうわけではないとか？？
もしこの辺りもわかったら追記するようにします。
それと今回は分散トランザクションが発生する前提で話を書きましたが、そもそもこの構成なら分散トランザクションは発生しないのが一番です。
System.Transactions.TransactionScopeを使用する際にはなるべく複数のDDLを書かないなど意識してみてもいいかもしれません。