開発者の目を通しおデヌタベヌスを操䜜する



デヌタベヌスを䜿甚しお新しい機胜を開発する堎合、開発サむクルには通垞、次の段階が含たれたすただし、これらに限定されたせん



。SQL移行の蚘述→コヌドの蚘述→テスト→リリヌス→監芖。



この蚘事では、品質を䜎䞋させるのではなく、向䞊させるず同時に、各段階でこのサむクルの時間を短瞮する方法に぀いお、いく぀かの実甚的なアドバむスを共有したいず思いたす。 



䌚瀟でPostgreSQLを䜿甚し、Javaでサヌバヌコヌドを蚘述しおいるため、䟋はこのスタックに基づいおいたすが、ほずんどのアむデアは䜿甚するデヌタベヌスやプログラミング蚀語に䟝存しおいたせん。



SQLの移行



蚭蚈埌の開発の最初の段階は、SQL移行の蚘述です。䞻なアドバむス-デヌタスキヌマに手動で倉曎を加えるこずはありたせんが、垞にスクリプトを介しお行い、それらを1か所に保存したす。 



圓瀟では、開発者がSQL移行を自分で䜜成するため、すべおの移行はメむンコヌドずずもにリポゞトリに保存されたす。䞀郚の䌁業では、デヌタベヌス管理者がスキヌマの倉曎に関䞎しおいたす。その堎合、移行レゞストリはどこかにありたす。いずれにせよ、このアプロヌチには次の利点がありたす。



  • 新しいベヌスを最初から簡単に䜜成したり、既存のベヌスを珟圚のバヌゞョンにアップグレヌドしたりできたす。これにより、新しいテスト環境ずロヌカル開発環境をすばやく展開できたす。
  • すべおの拠点のレむアりトは同じです。サヌビスに驚きはありたせん。
  • すべおの倉曎バヌゞョン管理の履歎がありたす。


、䞡方の商業および無料のこのプロセスを自動化するための倚くの既補のツヌルがありたすフラむりェむ、LiquiBaseを、sqitch私が比范しお、最高のツヌルを遞択しないであろう。この蚘事ではなどは、 -これは、別の倧きなテヌマである、そしおあなたはそれに倚くの蚘事を芋぀けるこずができたす..。 



私たちはフラむりェむを䜿甚しおいるので、ここにそれに぀いおの少しの情報がありたす



  • 移行には、sqlベヌスずjavaベヌスの2皮類がありたす。
  • SQLの移行は䞍倉䞍倉です。最初の実行埌、SQL移行は倉曎できたせん。Flywayは、移行ファむルの内容のチェックサムを蚈算し、実行するたびにそれを怜蚌したす。Javaの移行を䞍倉にするには、远加の手動操䜜が必芁です。
  • flyway_schema_history ( schema_version). , , , .


内郚契玄によるず、すべおのデヌタスキヌマの倉曎は、SQLの移行によっおのみ行われたす。それらの䞍倉性により、すべおの環境ず完党に同䞀の実際のスキヌマを垞に取埗できたす。 



Java移行は、玔粋なSQLで蚘述できないDMLにのみ䜿甚されたす。私たちにずっお、このような状況の兞型的な䟋は、別のデヌタベヌスからPostgresにデヌタを転送するための移行ですRedisからPostgresに移行しおいたすが、これはたったく別の話です。もう1぀の䟋は、倧きなテヌブルのデヌタを曎新するこずです。これは、テヌブルのロック時間を最小限に抑えるために耇数のトランザクションで実行されたす。 Postgresの11番目のバヌゞョンから、これはplpgsqlのSQLプロシヌゞャを䜿甚しお実行できるこずは蚀うたでもありたせん。



Javaコヌドが叀くなっおいる堎合、レガシヌを生成しないように移行を削陀できたすJava移行クラス自䜓は残りたすが、内郚は空です。私たちの囜では、これは本番環境ぞの移行埌1か月以内に発生する可胜性がありたす。これは、すべおのテスト環境ずロヌカル開発環境を曎新するのに十分な時間であるず考えおいたす。Javaの移行はDMLにのみ䜿甚されるため、それらを削陀しおも、新しいデヌタベヌスを最初から䜜成するこずにはたったく圱響しないこずに泚意しおください。



pg_bouncerを䜿甚する人にずっお重芁なニュアンス



Flywayは、移行䞭にロックを適甚しお、耇数の移行が同時に実行されないようにしたす。簡略化するず、次のように機胜したす。



  • ロックがキャプチャされおいたす 
  • 個別のトランザクションで移行を実行する
  • ブロックを解陀したす。 


Postgresの堎合、セッションモヌドでアドバむザリロックを䜿甚したす。぀たり、正しく機胜するには、ロックのキャプチャず解攟䞭にアプリケヌションサヌバヌが同じ接続で実行されおいる必芁がありたす。トランザクションモヌド最も䞀般的たたはシングルリク゚ストモヌドでpg_bouncerを䜿甚するず、トランザクションごずに新しい接続が返される堎合があり、flywayは確立されたロックを解攟できたせん。 



この問題を解決するために、セッションモヌドのpg_bouncerで個別の小さな接続プヌルを䜿甚したす。これは移行のみを目的ずしおいたす。アプリケヌションの偎からは、1぀の接続を含む別のプヌルもあり、リ゜ヌスを浪費しないように、移行埌にタむムアりトによっお閉じられたす。



コヌディング



移行が䜜成されたした。珟圚、コヌドを蚘述しおいたす。



アプリケヌション偎からデヌタベヌスを操䜜するには、次の3぀のアプロヌチがありたす。



  • ORMの䜿甚Javaに぀いお話す堎合、䌑止状態は事実䞊暙準です
  • プレヌンsql + jdbcTemplateなどを䜿甚したす。
  • DSLラむブラリの䜿甚。


ORMを䜿甚するず、SQLの知識の芁件を枛らすこずができたす-倚くが自動的に生成されたす 

  • デヌタスキヌマは、コヌドで䜿甚可胜なxml-descriptionたたはJava-entityから䜜成できたす。
  • オブゞェクトの関係は、宣蚀的な説明を䜿甚しお定矩されたす-ORMが結合を行いたす
  • Spring Data JPAを䜿甚するず、リポゞトリメ゜ッドの眲名に基づいおさらにトリッキヌなク゚リを自動的に生成するこずもできたす。


もう1぀の「ボヌナス」は、すぐに䜿甚できるデヌタキャッシュの存圚です䌑止状態の堎合、これらは3レベルのキャッシュです。



ただし、ORMは、他の匷力なツヌルず同様に、䜿甚するずきに特定の資栌が必芁であるこずに泚意するこずが重芁です。適切な構成がないず、コヌドは機胜する可胜性が高くなりたすが、最適ずは蚀えたせん。



反察に、SQLを手動で䜜成したす。これにより、リク゚ストを完党に制埡できたす。䜜成した内容が正確に実行され、驚くこずはありたせん。しかし、明らかに、これは手䜜業の量を増やし、開発者の資栌の芁件を増やしたす。



DSLラむブラリ



これらのアプロヌチのほが䞭間に、DSLラむブラリjOOQ、Querydslなどの䜿甚からなる別のアプロヌチがありたす。これらは通垞、ORMよりもはるかに軜量ですが、完党に手動のデヌタベヌス䜜業よりも䟿利です。DSLの䜿甚はあたり䞀般的ではないため、この蚘事ではこのアプロヌチに぀いお簡単に説明したす。 



ラむブラリの1぀であるjOOQに぀いお説明したす。圌女は䜕を提䟛しおいたすか



  • デヌタベヌス怜査ずクラスの自動生成
  • リク゚ストを曞くための流暢なAPI。


jOOQはORMではありたせん-ク゚リやキャッシングの自動生成はありたせんが、同時に、完党に手動のアプロヌチの問題のいく぀かは閉じられおいたす。

  • テヌブル、ビュヌ、関数などのクラス。デヌタベヌスオブゞェクトは自動的に生成されたす。 
  • リク゚ストはJavaで蚘述されおいるため、タむプセヌフが保蚌されたす。構文的に正しくないリク゚ストや、間違ったタむプのパラメヌタを持぀リク゚ストはコンパむルされたせん。IDEはすぐに゚ラヌを芁求し、アプリケヌションを起動しおリク゚ストの正確さを確認するために時間を無駄にする必芁はありたせん。これにより、開発プロセスがスピヌドアップし、゚ラヌの可胜性が枛少したす。


コヌドでは、リク゚ストは次のようになりたす。



BookRecord book = dslContext.selectFrom(BOOK)
                        .where(BOOK.LANGUAGE.eq("DE"))
                        .orderBy(BOOK.TITLE)
                        .fetchAny();


必芁に応じお、プレヌンsqlを䜿甚できたす。



Result<Record> records = dslContext.fetch("SELECT * FROM BOOK WHERE LANGUAGE = ? ORDER BY TITLE LIMIT 1", "DE");


明らかに、この堎合、ク゚リの正確さず結果の分析は完党にあなたの肩にかかっおいたす。



jOOQレコヌドずPOJO



䞊蚘の䟋のBookRecordは、ブックテヌブルの行のラッパヌであり、アクティブなレコヌドパタヌンを実装したす。このクラスは特定の実装を陀いおデヌタアクセスレむダヌの䞀郚であるため、アプリケヌションの他のレむダヌに転送したくない堎合がありたすが、独自のある皮のpojoオブゞェクトを䜿甚したす。レコヌドの倉換に䟿利なように、<–> pojo jooqには、自動ず手動のメカニズムがいく぀か甚意されおいたす。䞊蚘のリンクのドキュメントには、さたざたな読み取り䜿甚䟋がありたすが、新しいデヌタの挿入ず曎新の䟋はありたせん。このギャップを埋めたしょう 



private static final RecordUnmapper<Book, BookRecord> unmapper = 
    book -> new BookRecord(book.getTitle(), ...); // - 

public void create(Book book) {
    context.insertInto(BOOK)
            .set(unmapper.unmap(book))
            .execute();
}


ご芧のずおり、すべおが非垞に単玔です。



このアプロヌチにより、デヌタアクセスレむダヌクラス内の実装の詳现を非衚瀺にし、アプリケヌションの他のレむダヌぞの「リヌク」を回避できたす。 



たた、jooqは、䞀連の基本的なメ゜ッドを䜿甚しおDAOクラスを生成し、テヌブルデヌタの操䜜を簡玠化し、手動コヌドの量を枛らすこずができたすこれはSpring Data JPAず非垞によく䌌おいたす。



public interface DAO<R extends TableRecord<R>, P, T> {
    void insert(P object) throws DataAccessException;    
    void update(P object) throws DataAccessException;
    void delete(P... objects) throws DataAccessException;
    void deleteById(T... ids) throws DataAccessException;
    boolean exists(P object) throws DataAccessException;
    ...
}


䌚瀟では、DAOクラスの自動生成を䜿甚しおいたせん。デヌタベヌスオブゞェクトのラッパヌを生成し、自分でク゚リを䜜成するだけです。ラッパヌの生成は、移行が保存される別のmavenモゞュヌルが再構築されるたびに発生したす。少し埌で、これがどのように実装されるかに぀いおの詳现がありたす。



テスト



テストの䜜成は開発プロセスの重芁な郚分です。優れたテストはコヌドの品質を保蚌し、コヌドを維持しながら時間を節玄したす。同時に、その逆も圓おはたるず蚀っおも過蚀ではありたせん。テストが悪いず、高品質のコヌドの錯芚が生じ、゚ラヌが隠され、開発プロセスが遅くなる可胜性がありたす。したがっお、テストを䜜成するこずを決定するだけでは十分ではなく、正しく実行する必芁がありたす。同時に、テストの正確さの抂念は非垞に曖昧であり、誰もが少し独自のものを持っおいたす。 



テスト分類の問題に぀いおも同じこずが蚀えたす。この蚘事では、次の分割オプションの䜿甚を提案しおいたす。



  • ナニットテストナニットテスト 
  • 統合テスト
  • ゚ンドツヌ゚ンドテスト゚ンドツヌ゚ンド。


ナニットテストでは、個々のモゞュヌルの機胜を互いに分離しおチェックしたす。モゞュヌルのサむズも未定矩のものです。別のメ゜ッドである堎合もあれば、クラスである堎合もありたす。分離ずは、他のすべおのモゞュヌルがモックたたはスタブであるこずを意味したすロシア語では、これらは暡倣たたはスタブですが、どういうわけか、あたり良く聞こえたせん。このリンクをたどっお、2぀の違いに関するMartinFowlerの蚘事を読んでください。ナニットテストは小さくお高速ですが、個々のナニットのロゞックの正確さを保蚌するこずしかできたせん。



統合テストナニットテストずは異なり、耇数のモゞュヌルの盞互䜜甚をチェックしたす。デヌタベヌスの操䜜は、統合テストが理にかなっおいる堎合の良い䟋です。デヌタベヌスのすべおのニュアンスを考慮しお、高品質でデヌタベヌスを「ロック」するこずは非垞に難しいためです。ほずんどの堎合、統合テストは、他のタむプのテストず比范しお、デヌタベヌスをテストする際の実行速床ず品質保蚌の間の適切な劥協点です。したがっお、この蚘事では、このタむプのテストに぀いお詳しく説明したす。



゚ンドツヌ゚ンドのテストは最も広範囲です。それを実行するためには、環境党䜓を高める必芁がありたす。補品の品質に察する最高レベルの信頌性を保蚌したすが、最も遅く、最も高䟡です。



統合テスト



デヌタベヌスで動䜜するコヌドの統合テストに関しおは、ほずんどの開発者は、デヌタベヌスを起動する方法、初期デヌタで状態を初期化する方法、および可胜な限り迅速に実行する方法に぀いお自問したす。



少し前たでは、h2の䜿甚は統合テストではかなり䞀般的な方法でした。これは、Javaで蚘述されたメモリ内デヌタベヌスであり、最も䞀般的なデヌタベヌスずの互換性モヌドがありたす。デヌタベヌスをむンストヌルする必芁がなく、h2の汎甚性により、特にアプリケヌションが特定のデヌタベヌスに䟝存せず、SQL暙準に含たれおいるもののみを䜿甚する堎合垞にそうであるずは限りたせん、実際のデヌタベヌスの非垞に䟿利な代替品になりたした。 



ただし、問題は、h2でサポヌトが実装されおいないトリッキヌなデヌタベヌス機胜たたは新しいバヌゞョンからの完党に新しい機胜を䜿甚したずきに始たりたす。たた、䞀般に、これは特定のDBMSの「シミュレヌション」であるため、動䜜には垞にいく぀かの違いがありたす。



別のオプションは、埋め蟌たれたpostgresを䜿甚するこずです。これは実際のPostgresであり、アヌカむブずしお出荷され、むンストヌルは必芁ありたせん。これにより、通垞のPostgresバヌゞョンのように䜜業できたす。  YandexずopenTable



から最も人気のあるいく぀かの実装がありたす..。瀟内ではYandexのバヌゞョンを䜿甚したした。マむナス面のうち、起動時に非垞に時間がかかりたすアヌカむブが解凍され、デヌタベヌスが起動されるたびに、コンピュヌタヌの胜力にもよりたすが、2〜5秒かかりたす、公匏リリヌスバヌゞョンからの遅れにも問題がありたす。たた、コヌドを停止しようずした埌、゚ラヌが発生し、PostgresプロセスがOSでハングしたたたになるずいう問題に盎面したした。これは、手動で匷制終了する必芁がありたした。 



テストコンテナ



3番目のオプションはdockerを䜿甚するこずです。Javaの堎合、コヌドからdockerコンテナを操䜜するためのapiを提䟛するtestcontainersラむブラリがありたす。したがっお、dockerむメヌゞを持぀アプリケヌションの䟝存関係は、testcontainersを䜿甚したテストで眮き換えるこずができたす。たた、倚くの䞀般的なテクノロゞヌには、䜿甚するむメヌゞに応じお、より䟿利なapiを提䟛する個別の既補のクラスがありたす。



  • デヌタベヌスPostgres、Oracle、Cassandra、MongoDBなど、 
  • nginx
  • カフカなど


ちなみに、tescontainersプロゞェクトが非垞に人気になったずき、yandex開発者は、組み蟌みpostgresプロゞェクトの開発を停止するこずを公匏に発衚し、testcontainersに切り替えるようにアドバむスしたした。



長所は䜕ですか



  • testcontainersは高速です空のPostgresの起動には1秒もかかりたせん
  • postgresコミュニティは、新しいバヌゞョンごずに公匏のドッカヌ画像をリリヌスしたす
  • testcontainersには、プログラムで実行しない限り、jvmをシャットダりンした埌にぶら䞋がっおいるコンテナを匷制終了する特別なプロセスがありたす。
  • testcontainerを䜿甚するず、統䞀されたアプロヌチを䜿甚しおアプリケヌションの倖郚䟝存関係をテストできたす。これにより、明らかに䜜業が簡単になりたす。


Postgresを䜿甚したテスト 䟋



@Test
public void testSimple() throws SQLException {
    try (PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>()) {
        postgres.start();
        ResultSet resultSet = performQuery(postgres, "SELECT 1");
        int resultSetInt = resultSet.getInt(1);
        assertEquals("A basic SELECT query succeeds", 1, resultSetInt);
    }
}


testcontainersにむメヌゞ甚の個別のクラスがない堎合、コンテナヌの䜜成は次のようになりたす。



public static GenericContainer redis = new GenericContainer("redis:3.0.2")
            .withExposedPorts(6379);


JUnit4、JUnit5、たたはSpockを䜿甚しおいる堎合、testcontainersには远加の機胜がありたす。これらのフレヌムワヌクのサポヌト。これにより、テストの䜜成が容易になりたす。



テストコンテナを䜿甚したテストの高速化



埋め蟌たれたpostgresからtestcontainersに切り替えるず、Postgresをより速く実行するこずでテストがより速くなりたしたが、時間の経過ずずもにテストは再び遅くなり始めたした。これは、flywayが起動時に実行するSQL移行の数が増加したためです。移行回数が100を超えるず、実行時間は玄7〜8秒になり、テストの速床が倧幅に䜎䞋したした。それは次のように機胜したした



  1. 次のテストクラスの前に、Postgresを備えた「クリヌンな」コンテナが発売されたした
  2. フラむりェむは移行を実行したした
  3. このクラスのテストが実行されたした
  4. コンテナが停止され、削陀されたした
  5. 次のテストクラスに぀いお、項目1から繰り返したす。


明らかに、時間の経過ずずもに、2番目のステップにはたすたす時間がかかりたした。



この問題を解決しようずするず、すべおのテストの前に1回だけ移行を実行し、コンテナヌの状態を保存しおから、このコンテナヌをすべおのテストで䜿甚するだけで十分であるこずがわかりたした。そのため、アルゎリズムが倉曎されたした。



  1. すべおのテストの前に、Postgresを備えた「クリヌンな」コンテナが起動されたす
  2. flywayは移行を実行したす
  3. コンテナの状態が持続する
  4. 次のテストクラスの前に、事前に準備されたコンテナが起動されたす
  5. このクラスのテストが実行されたす
  6. コンテナが停止し、削陀されたす
  7. 次のテストクラスに぀いお、手順4から繰り返したす。


珟圚、個々のテストの実行時間は移行の数に䟝存せず、珟圚の移行の数200以䞊では、新しいスキヌムはすべおのテストの実行ごずに数分を節玄したす。



これを実装する方法に関する技術的な詳现を次に瀺したす。



Dockerには、commitコマンドを䜿甚しお実行䞭のコンテナヌから新しいむメヌゞを䜜成するための組み蟌みメカニズムがありたす。たずえば、蚭定を倉曎するこずで、画像をカスタマむズできたす。 



重芁なニュアンスは、コマンドがマりントされたパヌティションのデヌタを保存しないこずです。ただし、公匏のPostgres dockerむメヌゞを取埗するず、デヌタが保存されおいるPGDATAディレクトリは別のセクションに配眮されるためコンテナヌの再起動埌にデヌタが倱われないようにするため、コミットが実行されたずきにデヌタベヌス自䜓の状態は保存されたせん。 



解決策は簡単です。PGDATAのセクションは䜿甚せず、デヌタをメモリに保持したす。これは、テストではごく普通のこずです。これを行うには2぀の方法がありたす-dockerfileを䜿甚したすこのようなものセクションを䜜成せずに、たたは公匏コンテナの開始時にPGDATA倉数をオヌバヌラむドしたすセクションは残りたすが、䜿甚されたせん。2番目の方法ははるかに簡単に芋えたす



PostgreSQLContainer<?> container = ...
container.addEnv("PGDATA", "/var/lib/postgresql/data-no-mounted");
container.start();


コミットする前に、postgresをチェックポむントしお、倉曎を共有バッファヌから「ディスク」オヌバヌラむドされたPGDATA倉数に察応にフラッシュするこずをお勧めしたす。



container.execInContainer("psql", "-c", "checkpoint");


コミット自䜓は次のようになりたす。



CommitCmd cmd = container.getDockerClient().commitCmd(container.getContainerId())
                .withMessage("Container for integration tests. ...")
                .withRepository(imageName)
                .withTag(tag);
String imageId = cmd.exec();


準備された画像を䜿甚するこのアプロヌチは、他の倚くの画像にも適甚できるため、統合テストを実行する際の時間を節玄できるこずは泚目に倀したす。



ビルド時間の最適化に぀いおもう少し



前述のように、移行を䜿甚しお個別のmavenモゞュヌルをアセンブルする堎合、特に、デヌタベヌスオブゞェクトに察しおjavaラッパヌが生成されたす。このために、メむンコヌドをコンパむルする前に起動され、次の3぀のアクションを実行する自䜜のmavenプラグむンが䜿甚されたす。



  1. postgresで「クリヌンな」ドッカヌコンテナを実行したす
  2. Flywayを起動したす。これは、すべおのデヌタベヌスに察しおsql移行を実行し、それによっおそれらの有効性をチェックしたす。
  3. Jooqを実行したす。これは、デヌタベヌススキヌマを怜査し、テヌブル、ビュヌ、関数、およびその他のスキヌマオブゞェクトのJavaクラスを生成したす。


簡単にわかるように、最初の2぀の手順は、テストの実行時に実行される手順ず同じです。コンテナの起動ずテスト前の移行の実行にかかる時間を節玄するために、コンテナの状態の保存をプラグむンに移動したした。したがっお、モゞュヌルを再構築した盎埌に、コヌドで䜿甚されおいるすべおのデヌタベヌスの統合テスト甚の既補のむメヌゞが、dockerむメヌゞのロヌカルリポゞトリに衚瀺されたす。



より詳现なコヌド䟋
@ThreadSafe
public class PostgresContainerAdapter implements PostgresExecutable {
  private static final String ORIGINAL_IMAGE = "postgres:11.6-alpine";

  @GuardedBy("this")
  @Nullable
  private PostgreSQLContainer<?> container; // not null if it is running

  @Override
  public synchronized String start(int port, String db, String user, String password) 
  {
    Preconditions.checkState(container == null, "postgres is already running");

    PostgreSQLContainer<?> newContainer = new PostgreSQLContainer<>(ORIGINAL_IMAGE)
        .withDatabaseName(db)
        .withUsername(user)
        .withPassword(password);

    newContainer.addEnv("PGDATA", "/var/lib/postgresql/data-no-mounted");

    // workaround for using fixed port instead of random one chosen by docker
    List<String> portBindings = new ArrayList<>(newContainer.getPortBindings());
    portBindings.add(String.format("%d:%d", port, POSTGRESQL_PORT));
    newContainer.setPortBindings(portBindings);
    newContainer.start();

    container = newContainer;
    return container.getJdbcUrl();
  }

  @Override
  public synchronized void saveState(String name) {
    try {
      Preconditions.checkState(container != null, "postgres isn't started yet");

      // flush all changes
      doCheckpoint(container);

      commitContainer(container, name);
    } catch (Exception e) {
      stop();
      throw new RuntimeException("Saving postgres container state failed", e);
    }
  }

  @Override
  public synchronized void stop() {
    Preconditions.checkState(container != null, "postgres isn't started yet");

    container.stop();
    container = null;
  }

  private static void doCheckpoint(PostgreSQLContainer<?> container) {
    try {
      container.execInContainer("psql", "-c", "checkpoint");
    } catch (IOException | InterruptedException e) {
      throw new RuntimeException(e);
    }
  }

  private static void commitContainer(PostgreSQLContainer<?> container, String image)
  {
    String tag = "latest";
    container.getDockerClient().commitCmd(container.getContainerId())
        .withMessage("Container for integration tests. It uses non default location for PGDATA which is not mounted to a volume")
        .withRepository(image)
        .withTag(tag)
        .exec();
  }
  // ...
}


( «start»):

@Mojo(name = "start")
public class PostgresPluginStartMojo extends AbstractMojo {
  private static final Logger logger = LoggerFactory.getLogger(PostgresPluginStartMojo.class);

  @Nullable
  static PostgresExecutable postgres;

  @Parameter(defaultValue = "5432")
  private int port;
  @Parameter(defaultValue = "dbName")
  private String db;
  @Parameter(defaultValue = "userName")
  private String user;
  @Parameter(defaultValue = "password")
  private String password;

  @Override
  public void execute() throws MojoExecutionException {
    if (postgres != null) { 
      logger.warn("Postgres already started");
      return;
    }
    logger.info("Starting Postgres");
    if (!isDockerInstalled()) {
      throw new IllegalStateException("Docker is not installed");
    }
    String url = start();
    testConnection(url, user, password);
    logger.info("Postgres started at " + url);
  }

  private String start() {
    postgres = new PostgresContainerAdapter();
    return postgres.start(port, db, user, password);
  }

  private static void testConnection(String url, String user, String password) throws MojoExecutionException {
    try (Connection conn = DriverManager.getConnection(url, user, password)) {
      conn.createStatement().execute("SELECT 1");
    } catch (SQLException e) {
      throw new MojoExecutionException("Exception occurred while testing sql connection", e);
    }
  }

  private static boolean isDockerInstalled() {
    if (CommandLine.executableExists("docker")) {
      return true;
    }
    if (CommandLine.executableExists("docker.exe")) {
      return true;
    }
    if (CommandLine.executableExists("docker-machine")) {
      return true;
    }
    if (CommandLine.executableExists("docker-machine.exe")) {
      return true;
    }
    return false;
  }
}


save-state stop .



:



<build>
  <plugins>
    <plugin>
      <groupId>com.miro.maven</groupId>
      <artifactId>PostgresPlugin</artifactId>
      <executions>
        <!-- running a postgres container -->
        <execution>
          <id>start-postgres</id>
          <phase>generate-sources</phase>
          <goals>
            <goal>start</goal>
          </goals>
          
          <configuration>
            <db>${db}</db>
            <user>${dbUser}</user>
            <password>${dbPassword}</password>
            <port>${dbPort}</port>
          </configuration>
        </execution>
        
        <!-- applying migrations and generation java-classes -->
        <execution>
          <id>flyway-and-jooq</id>
          <phase>generate-sources</phase>
          <goals>
            <goal>execute-mojo</goal>
          </goals>
          
          <configuration>
            <plugins>
              <!-- applying migrations -->
              <plugin>
                <groupId>org.flywaydb</groupId>
                <artifactId>flyway-maven-plugin</artifactId>
                <version>${flyway.version}</version>
                <executions>
                  <execution>
                    <id>migration</id>
                    <goals>
                      <goal>migrate</goal>
                    </goals>
                    
                    <configuration>
                      <url>${dbUrl}</url>
                      <user>${dbUser}</user>
                      <password>${dbPassword}</password>
                      <locations>
                        <location>filesystem:src/main/resources/migrations</location>
                      </locations>
                    </configuration>
                  </execution>
                </executions>
              </plugin>

              <!-- generation java-classes -->
              <plugin>
                <groupId>org.jooq</groupId>
                <artifactId>jooq-codegen-maven</artifactId>
                <version>${jooq.version}</version>
                <executions>
                  <execution>
                    <id>jooq-generate-sources</id>
                    <goals>
                      <goal>generate</goal>
                    </goals>
                      
                    <configuration>
                      <jdbc>
                        <url>${dbUrl}</url>
                        <user>${dbUser}</user>
                        <password>${dbPassword}</password>
                      </jdbc>
                      
                      <generator>
                        <database>
                          <name>org.jooq.meta.postgres.PostgresDatabase</name>
                          <includes>.*</includes>
                          <excludes>
                            #exclude flyway tables
                            schema_version | flyway_schema_history
                            # other excludes
                          </excludes>
                          <includePrimaryKeys>true</includePrimaryKeys>
                          <includeUniqueKeys>true</includeUniqueKeys>
                          <includeForeignKeys>true</includeForeignKeys>
                          <includeExcludeColumns>true</includeExcludeColumns>
                        </database>
                        <generate>
                          <interfaces>false</interfaces>
                          <deprecated>false</deprecated>
                          <jpaAnnotations>false</jpaAnnotations>
                          <validationAnnotations>false</validationAnnotations>
                        </generate>
                        <target>
                          <packageName>com.miro.persistence</packageName>
                          <directory>src/main/java</directory>
                        </target>
                      </generator>
                    </configuration>
                  </execution>
                </executions>
              </plugin>
            </plugins>
          </configuration>
        </execution>

        <!-- creation an image for integration tests -->
        <execution>
          <id>save-state-postgres</id>
          <phase>generate-sources</phase>
          <goals>
            <goal>save-state</goal>
          </goals>
          
          <configuration>
            <name>postgres-it</name>
          </configuration>
        </execution>

        <!-- stopping the container -->
        <execution>
          <id>stop-postgres</id>
          <phase>generate-sources</phase>
          <goals>
            <goal>stop</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>




リリヌス



コヌドが曞かれ、テストされたした-リリヌスする時が来たした。䞀般に、リリヌスの耇雑さは次の芁因によっお異なりたす。



  • デヌタベヌスの数1぀以䞊
  • デヌタベヌスのサむズに぀いお
  • アプリケヌションサヌバヌの数1぀以䞊
  • シヌムレスリリヌスかどうかアプリケヌションのダりンタむムが蚱可されおいるかどうか。


ほずんどの堎合、すべおのデヌタベヌスずすべおのアプリケヌションサヌバヌを同時に曎新するこずは䞍可胜であるため、項目1ず3は、コヌドに䞋䜍互換性の芁件を課したす。デヌタベヌスのスキヌマが異なり、サヌバヌのコヌドのバヌゞョンが異なる堎合が垞にありたす。



デヌタベヌスのサむズは移行時間に圱響したす。デヌタベヌスが倧きいほど、長い移行を実行する必芁が生じる可胜性が高くなりたす。



シヌムレス性は郚分的に結果ずしお生じる芁因です-リリヌスがシャットダりンダりンタむムで実行される堎合、最初の3぀のポむントはそれほど重芁ではなく、アプリケヌションが利甚できない時間にのみ圱響したす。



私たちのサヌビスに぀いお話す堎合、これらは次のずおりです。



  • 箄30のデヌタベヌスクラスタヌ


  • 1぀のベヌスのサむズ200〜400 GB
  • ( 100),
  • .


カナリアリリヌス を䜿甚したす。アプリケヌションの新しいバヌゞョンが最初に少数のサヌバヌに衚瀺されプレリリヌスず呌ばれたす、しばらくしお、プレリリヌスで゚ラヌが芋぀からない堎合は、他のサヌバヌにリリヌスされたす。したがっお、実皌働サヌバヌはさたざたなバヌゞョンで実行できたす。



起動時に、各アプリケヌションサヌバヌは、゜ヌスコヌドにあるスクリプトのバヌゞョンを䜿甚しおデヌタベヌスのバヌゞョンをチェックしたすフラむりェむの芳点から、これは怜蚌ず呌ばれたす。それらが異なる堎合、サヌバヌは起動したせん。これにより、コヌドずデヌタベヌスの互換性が保蚌されたす。たずえば、移行がサヌバヌの別のバヌゞョンで行われおいるために、コヌドがただ䜜成されおいないテヌブルで機胜する堎合、状況は発生したせん。



ただし、たずえば、新しいバヌゞョンのアプリケヌションで、叀いバヌゞョンのサヌバヌで䜿甚できるテヌブルの列を削陀する移行がある堎合、これはもちろん問題を解決したせん。珟圚、このような状況はレビュヌ段階でのみチェックしおいたす必須ですが、友奜的な方法で远加を導入する必芁がありたす。CI / CDサむクルでこのようなチェックを行うステヌゞ。  



移行に時間がかかる堎合がありたずえば、倧きなテヌブルからデヌタを曎新する堎合、同時にリリヌスの速床を䜎䞋させないために、組み合わせ移行の手法を䜿甚したす。..。この組み合わせは、実行䞭のサヌバヌで手動で移行を実行し管理パネルを介しお、フラむりェむなしで、したがっお移行履歎に蚘録せずに、サヌバヌの次のバヌゞョンで同じ移行の「通垞の」出力を実行するこずで構成されたす。これらの移行には、次の芁件が適甚されたす。



  • たず、長時間の実行䞭にアプリケヌションをブロックしないように䜜成する必芁がありたすここでの重芁なポむントは、DBレベルで長期ロックを取埗しないこずです。これを行うために、移行の蚘述方法に関する開発者向けの内郚ガむドラむンがありたす。将来的には、Habréでも共有する可胜性がありたす。
  • 次に、「通垞の」起動䞭の移行では、すでに手動モヌドで実行されおいるこずを確認し、この堎合は䜕も実行しないでください。履歎に新しいレコヌドをコミットするだけです。SQL移行の堎合、このようなチェックは、倉曎に぀いおSQLク゚リを実行するこずによっお実行されたす。Java移行の別のアプロヌチは、手動実行埌に蚭定される保存されたブヌルフラグを䜿甚するこずです。




このアプロヌチは2぀の問題を解決したす

  • リリヌスは高速です手動アクションではありたすが
  • ( ) - .




リリヌスされるず、開発サむクルは終了したせん。新しい機胜が機胜するかどうかおよびどのように機胜するかを理解するには、メトリックで「囲む」必芁がありたす。それらは、ビゞネスずシステムの2぀のグルヌプに分けるこずができたす。 



最初のグルヌプはサブゞェクト領域に匷く䟝存したす。メヌルサヌバヌの堎合、送信された文字の数、ニュヌスリ゜ヌスの堎合、1日あたりの䞀意のナヌザヌの数などを知っおおくず䟿利です。



2番目のグルヌプのメトリックは、サヌバヌの技術的状態cpu、メモリ、ネットワヌク、デヌタベヌスなどを決定したす。



正確に監芖する必芁があるものずその方法は、すべおの人にずっおほが同じです。 ã“れは、膚倧な数の個別の蚘事のトピックであり、ここでは觊れたせん。最も基本的なキャプテンでさえこずだけを思い出させたい



事前にメトリックを定矩する



基本的なメトリックのリストを定矩する必芁がありたす。たた、システムで䜕が起こっおいるのかがわからない堎合は、最初のむンシデントの埌ではなく、リリヌス前に事前に行う必芁がありたす。



自動アラヌトを蚭定する



これにより、反応時間が短瞮され、手動監芖の時間が節玄されたす。理想的には、ナヌザヌが問題を感じおあなたに手玙を曞く前に、問題に぀いお知っおおく必芁がありたす。



すべおのノヌドからメトリックを収集する



ログのように、メトリックが倚すぎるこずはありたせん。システムの各ノヌドアプリケヌションサヌバヌ、デヌタベヌス、接続プヌラヌ、バランサヌなどからのデヌタの存圚により、その状態の党䜓像を把握でき、必芁に応じお、問題をすばやく特定できたす。 



簡単な䟋Webペヌゞのデヌタの読み蟌みが遅くなり始めたした。倚くの理由が考えられたす



  • Webサヌバヌが過負荷になり、芁求ぞの応答に時間がかかりたす


  • SQLク゚リの実行に時間がかかる
  • キュヌが接続プヌルに蓄積され、アプリケヌションサヌバヌが長時間接続を受信できない
  • ネットワヌクの問題
  • 他の䜕か


指暙がなければ、問題の根本原因を芋぀けるのは簡単ではありたせん。



完了の代わりに



特効薬がなく、どちらのアプロヌチを遞択するかは特定のタスクの芁件に䟝存し、他の人にずっおうたくいくこずはあなたには圓おはたらないかもしれないずいう事実に぀いお、非垞に平凡な蚀い方をしたいず思いたす。しかし、あなたが知っおいるアプロヌチが倚ければ倚いほど、この遞択をより培底的か぀定性的に行うこずができたす。この蚘事から、あなたが将来あなたを助けるであろうあなた自身のために䜕か新しいこずを孊んだこずを願っおいたす。デヌタベヌスの操䜜プロセスを改善するためにどのようなアプロヌチを䜿甚しおいるかに぀いおコメントさせおいただきたす。



All Articles