├── .DS_Store ├── .gitignore ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── Dockerfile ├── Dockerfile.arm ├── LICENSE ├── README.md ├── config └── ddl.sql ├── docker-compose-arm.yml ├── docker-compose-local.yml ├── docker-compose.yml ├── mvnw ├── mvnw.cmd ├── nginx.conf ├── pom.xml ├── src └── main │ ├── java │ └── com │ │ └── hugomarques │ │ └── rinhabackend2023 │ │ ├── CacheConfig.java │ │ ├── LoadDatabase.java │ │ ├── RinhaBackend2023Application.java │ │ └── pessoas │ │ ├── Pessoa.java │ │ ├── PessoaController.java │ │ ├── PessoaErrorsAdvice.java │ │ ├── PessoaNotFoundException.java │ │ ├── PessoaRepository.java │ │ └── StringListConverter.java │ └── resources │ └── application.properties └── stress-test ├── .gitignore ├── README.md ├── geracao_recursos.py ├── run-test-aws.sh ├── run-test.sh └── user-files ├── resources ├── pessoas-payloads.tsv └── termos-busca.tsv └── simulations └── rinhabackend └── RinhaBackendSimulation.scala /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hugomarques/rinha-backend-2023-q3-java/884efab1752f3703d2a848a4be59cf3c2dec6d1e/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | /.mvn/ 35 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hugomarques/rinha-backend-2023-q3-java/884efab1752f3703d2a848a4be59cf3c2dec6d1e/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.4/apache-maven-3.9.4-bin.zip 18 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar 19 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:latest as BUILD 2 | 3 | # Install Oracle OpenJDK 21 4 | RUN apt -y update && apt -y upgrade && apt -y install wget 5 | RUN wget https://download.java.net/java/GA/jdk21/fd2272bbf8e04c3dbaee13770090416c/35/GPL/openjdk-21_linux-x64_bin.tar.gz 6 | RUN tar zxvf openjdk-21_linux-x64_bin.tar.gz 7 | ENV JAVA_HOME=/jdk-21 8 | ENV PATH="$JAVA_HOME/bin:${PATH}" 9 | 10 | WORKDIR /app 11 | 12 | COPY src /app/src 13 | COPY pom.xml /app 14 | COPY .mvn /app/.mvn 15 | COPY mvnw /app/mvnw 16 | 17 | RUN ./mvnw clean package -DskipTests 18 | 19 | FROM ubuntu:latest as RUNTIME 20 | 21 | COPY --from=BUILD /app/target/rinhabackend2023-0.0.1-SNAPSHOT.jar /rinha.jar 22 | COPY --from=BUILD /jdk-21 /jdk-21 23 | 24 | ENV JAVA_HOME=/jdk-21 25 | ENV PATH="$JAVA_HOME/bin:${PATH}" 26 | 27 | EXPOSE 8080 28 | 29 | 30 | ENTRYPOINT [ "java", "-XX:+UseParallelGC", "-XX:MaxRAMPercentage=75", "--enable-preview", "-jar", "./rinha.jar" ] 31 | -------------------------------------------------------------------------------- /Dockerfile.arm: -------------------------------------------------------------------------------- 1 | # Build project 2 | FROM --platform=arm64 ubuntu:latest as BUILD 3 | 4 | RUN apt -y update && apt -y upgrade && apt -y install wget 5 | RUN wget https://download.java.net/java/GA/jdk21/fd2272bbf8e04c3dbaee13770090416c/35/GPL/openjdk-21_linux-aarch64_bin.tar.gz 6 | RUN tar zxvf openjdk-21_linux-aarch64_bin.tar.gz 7 | 8 | ENV JAVA_HOME=/jdk-21 9 | ENV PATH="$JAVA_HOME/bin:${PATH}" 10 | 11 | WORKDIR /app 12 | 13 | COPY src /app/src 14 | COPY pom.xml /app 15 | COPY .mvn /app/.mvn 16 | COPY mvnw /app/mvnw 17 | 18 | RUN ./mvnw clean package -DskipTests 19 | 20 | # Runtime image 21 | FROM --platform=arm64 ubuntu:latest as RUNTIME 22 | 23 | COPY --from=BUILD /app/target/rinhabackend2023-0.0.1-SNAPSHOT.jar /rinha.jar 24 | COPY --from=BUILD /jdk-21 /jdk-21 25 | 26 | ENV JAVA_HOME=/jdk-21 27 | ENV PATH="$JAVA_HOME/bin:${PATH}" 28 | 29 | EXPOSE 8080 30 | 31 | ENTRYPOINT [ "java", "--enable-preview", "-jar", "./rinha.jar" ] 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rinha-backend-2023-q3-java 2 | 3 | Backend pra rinha backend 2023 q3. 4 | 5 | Mais informações sobre a rinha no Repo: https://github.com/zanfranceschi/rinha-de-backend-2023-q3 6 | 7 | ## Implementação 8 | 9 | Esse repo tem 3 implementações (completas ou não). 10 | 11 | A branch spring-tradicional contém uma implementação feijão com arroz: 12 | - Spring boot 13 | - Postgresql 14 | - Redis 15 | - Uso de Java 21 pra usar o Project Loom (veja o arquivo ThreadConfig.java) 16 | 17 | A branch jdbc contém as seguintes mudanças: 18 | - Spring boot 19 | - Spring Data Jdbc 20 | - Uso de Java 21 também pelo project Loom. 21 | 22 | A branch main/flux implementa a solução com o paradigma reativo: 23 | - Spring Boot Webflux 24 | - R2dbc 25 | - Postgresql (e o seu devido driver reativo) 26 | - Redis 27 | 28 | ## Requisitos 29 | 30 | Para rodar, precisamos ter instalado: 31 | 32 | * docker 33 | * Gatling (versão usada [3.9.5](https://repo1.maven.org/maven2/io/gatling/highcharts/gatling-charts-highcharts-bundle/3.9.5/) 34 | 35 | 36 | ## Para rodar localmente 37 | 38 | 1. Subir o ambiente (Postgresql + Redis) com o docker : 39 | 40 | Na raiz do projeto, rodar: 41 | 42 | `` docker compose -f docker-compose-local.yml up `` 43 | 44 | 2. Subir a aplicação do Spring Boot (pela IDE ou por terminal) 45 | 46 | `` MAVEN_OPTS="-Xmx1500m --enable-preview" mvn spring-boot:run `` 47 | 48 | 3. Rode os testes do Galting 49 | 50 | `` 51 | cd stress-test 52 | ./run-tests.sh 53 | `` 54 | 55 | Tudo isso dá para ser feito dentro do IntelliJ tb se preferir. 56 | 57 | A cada teste remova os volumes criados pelo Docker. 58 | 59 | ## Para rodar tudo junto (como limitação de CPU) 60 | 61 | 1. Subir o ambiente (Postgresql + Redis) com o docker : 62 | 63 | Na raiz do projeto, rodar: 64 | 65 | `` docker compose up --build`` 66 | 67 | 2. Rode os testes do Galting 68 | 69 | `` 70 | cd stress-test 71 | ./run-tests.sh 72 | `` 73 | 74 | 3. (Opcional) Recomendo monitorar seus containers através do comando: 75 | 76 | ``docker stats`` 77 | 78 | ## Referências 79 | 80 | 1. https://github.com/willy-r/rinha-de-backend-2023-javinha 81 | 2. https://github.com/boaglio/rinha-backend-2023-q3-java 82 | 3. https://github.com/brunoborges/rinha-app 83 | 84 | ## Agradecimentos 85 | 86 | Aos [@BrunoBorges](https://github.com/brunoborges) pela mentoria, ao [@Boaglio](https://github.com/boaglio) pela base pra rodar reativo. 87 | 88 | E claro, ao grande [@zanfranceschi](https://github.com/zanfranceschi) por organizar esse desafio. -------------------------------------------------------------------------------- /config/ddl.sql: -------------------------------------------------------------------------------- 1 | SET statement_timeout = 0; 2 | SET lock_timeout = 0; 3 | SET idle_in_transaction_session_timeout = 0; 4 | SET client_encoding = 'UTF8'; 5 | SET standard_conforming_strings = on; 6 | SELECT pg_catalog.set_config('search_path', '', false); 7 | SET check_function_bodies = false; 8 | SET xmloption = content; 9 | SET client_min_messages = warning; 10 | SET row_security = off; 11 | 12 | ALTER SCHEMA PUBLIC OWNER TO rinha; 13 | 14 | SET default_tablespace = ''; 15 | 16 | SET default_table_access_method = heap; 17 | 18 | DROP TABLE IF EXISTS PUBLIC."pessoas"; 19 | 20 | CREATE TABLE public."pessoas" ( 21 | id UUID PRIMARY KEY NOT NULL, 22 | apelido VARCHAR(32) UNIQUE NOT NULL, 23 | nome VARCHAR(100) NOT NULL, 24 | nascimento VARCHAR(10) NOT NULL, 25 | stack VARCHAR(255) NULL 26 | ); 27 | 28 | CREATE UNIQUE INDEX idx_apelido ON PUBLIC."pessoas" (apelido); 29 | 30 | CREATE EXTENSION IF NOT EXISTS pg_trgm SCHEMA pg_catalog; 31 | 32 | CREATE INDEX idx_pessoas_apelido_trgm ON PUBLIC."pessoas" USING gin("apelido" gin_trgm_ops); 33 | CREATE INDEX idx_pessoas_nome_trgm ON PUBLIC."pessoas" USING gin("nome" gin_trgm_ops); 34 | 35 | ALTER TABLE PUBLIC."pessoas" OWNER TO rinha; -------------------------------------------------------------------------------- /docker-compose-arm.yml: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | name: 'rinha-de-backend' 3 | 4 | services: 5 | spring-api1: 6 | build: 7 | context: . 8 | dockerfile: Dockerfile.arm 9 | hostname: spring-api1 10 | environment: 11 | - SERVER_PORT=8080 12 | - DATABASE_URL=jdbc:postgresql://db-postgresql:5432/rinhadb 13 | - DATABASE_USERNAME=rinha 14 | - DATABASE_PASSWORD=rinha123 15 | - REDIS_HOST=cache 16 | depends_on: 17 | - db-postgresql 18 | - cache 19 | ulimits: 20 | nofile: 21 | soft: 1000000 22 | hard: 1000000 23 | deploy: 24 | resources: 25 | limits: 26 | cpus: '0.6' 27 | memory: '1.25GB' 28 | networks: 29 | - app-network 30 | 31 | spring-api2: 32 | build: 33 | context: . 34 | dockerfile: Dockerfile.arm 35 | hostname: spring-api2 36 | environment: 37 | - SERVER_PORT=8080 38 | - DATABASE_URL=jdbc:postgresql://db-postgresql:5432/rinhadb 39 | - DATABASE_USERNAME=rinha 40 | - DATABASE_PASSWORD=rinha123 41 | - REDIS_HOST=cache 42 | depends_on: 43 | - db-postgresql 44 | - cache 45 | ulimits: 46 | nofile: 47 | soft: 1000000 48 | hard: 1000000 49 | deploy: 50 | resources: 51 | limits: 52 | cpus: '0.6' 53 | memory: '1.25GB' 54 | networks: 55 | - app-network 56 | 57 | nginx: 58 | image: nginx:latest 59 | volumes: 60 | - ./nginx.conf:/etc/nginx/nginx.conf:ro 61 | depends_on: 62 | - spring-api1 63 | - spring-api2 64 | ports: 65 | - "9999:9999" 66 | networks: 67 | - app-network 68 | deploy: 69 | resources: 70 | limits: 71 | cpus: '0.2' 72 | memory: '0.25GB' 73 | 74 | cache: 75 | hostname: cache 76 | image: redis:latest 77 | ports: 78 | - '6379:6379' 79 | deploy: 80 | resources: 81 | limits: 82 | cpus: '0.05' 83 | memory: '0.25GB' 84 | environment: 85 | - ALLOW_EMPTY_PASSWORD=yes 86 | - REDIS_DISABLE_COMMANDS=FLUSHDB,FLUSHALL 87 | networks: 88 | - app-network 89 | 90 | db-postgresql: 91 | image: postgres:latest 92 | command: postgres -c 'max_connections=600' 93 | hostname: db 94 | environment: 95 | - POSTGRES_PASSWORD=rinha123 96 | - POSTGRES_USER=rinha 97 | - POSTGRES_DB=rinhadb 98 | ports: 99 | - "5432:5432" 100 | deploy: 101 | resources: 102 | limits: 103 | cpus: '0.05' 104 | memory: '0.5GB' 105 | networks: 106 | - app-network 107 | 108 | networks: 109 | app-network: -------------------------------------------------------------------------------- /docker-compose-local.yml: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | 3 | services: 4 | db-postgresql: 5 | image: postgres:latest 6 | command: "postgres -c max_connections=200 -c shared_buffers=256MB -c synchronous_commit=off -c fsync=off -c full_page_writes=off" 7 | hostname: db-postgresql 8 | volumes: 9 | - ./config/ddl.sql:/docker-entrypoint-initdb.d/ddl.sql 10 | ports: 11 | - "2345:5432" 12 | environment: 13 | POSTGRES_USER: rinha 14 | POSTGRES_PASSWORD: rinha123 15 | POSTGRES_DB: rinhadb 16 | cache: 17 | hostname: cache 18 | image: redis:latest 19 | command: [ "redis-server", "--appendonly", "no", "--save ''" ] 20 | ports: 21 | - '6379:6379' -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | name: 'rinha-de-backend' 3 | 4 | services: 5 | spring-api1: 6 | build: . 7 | hostname: spring-api1 8 | environment: 9 | - SERVER_PORT=8080 10 | - DATABASE_URL=r2dbc:postgresql://db-postgresql:5432/rinhadb 11 | - DATABASE_USERNAME=rinha 12 | - DATABASE_PASSWORD=rinha123 13 | - REDIS_HOST=cache 14 | depends_on: 15 | - db-postgresql 16 | - cache 17 | ulimits: 18 | nofile: 19 | soft: 1000000 20 | hard: 1000000 21 | deploy: 22 | resources: 23 | limits: 24 | cpus: '0.4' 25 | memory: '1GB' 26 | networks: 27 | - app-network 28 | 29 | spring-api2: 30 | build: . 31 | hostname: spring-api2 32 | environment: 33 | - SERVER_PORT=8080 34 | - DATABASE_URL=r2dbc:postgresql://db-postgresql:5432/rinhadb 35 | - DATABASE_USERNAME=rinha 36 | - DATABASE_PASSWORD=rinha123 37 | - REDIS_HOST=cache 38 | depends_on: 39 | - db-postgresql 40 | - cache 41 | ulimits: 42 | nofile: 43 | soft: 1000000 44 | hard: 1000000 45 | deploy: 46 | resources: 47 | limits: 48 | cpus: '0.4' 49 | memory: '1GB' 50 | networks: 51 | - app-network 52 | 53 | nginx: 54 | image: nginx:latest 55 | volumes: 56 | - ./nginx.conf:/etc/nginx/nginx.conf:ro 57 | depends_on: 58 | - spring-api1 59 | - spring-api2 60 | ports: 61 | - "9999:9999" 62 | networks: 63 | - app-network 64 | deploy: 65 | resources: 66 | limits: 67 | cpus: '0.2' 68 | memory: '0.25GB' 69 | 70 | cache: 71 | hostname: cache 72 | image: redis:latest 73 | command: ["redis-server", "--appendonly", "no", "--save ''"] 74 | ports: 75 | - '6379:6379' 76 | deploy: 77 | resources: 78 | limits: 79 | cpus: '0.05' 80 | memory: '0.1GB' 81 | networks: 82 | - app-network 83 | 84 | db-postgresql: 85 | image: postgres:latest 86 | command: "postgres -c max_connections=200 -c shared_buffers=256MB -c synchronous_commit=off -c fsync=off -c full_page_writes=off" 87 | hostname: db-postgresql 88 | volumes: 89 | - ./config/ddl.sql:/docker-entrypoint-initdb.d/ddl.sql 90 | environment: 91 | - POSTGRES_PASSWORD=rinha123 92 | - POSTGRES_USER=rinha 93 | - POSTGRES_DB=rinhadb 94 | ports: 95 | - "5432:5432" 96 | deploy: 97 | resources: 98 | limits: 99 | cpus: '0.5' 100 | memory: '1GB' 101 | networks: 102 | - app-network 103 | 104 | networks: 105 | app-network: -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Apache Maven Wrapper startup batch script, version 3.2.0 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | # e.g. to debug Maven itself, use 32 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | # ---------------------------------------------------------------------------- 35 | 36 | if [ -z "$MAVEN_SKIP_RC" ] ; then 37 | 38 | if [ -f /usr/local/etc/mavenrc ] ; then 39 | . /usr/local/etc/mavenrc 40 | fi 41 | 42 | if [ -f /etc/mavenrc ] ; then 43 | . /etc/mavenrc 44 | fi 45 | 46 | if [ -f "$HOME/.mavenrc" ] ; then 47 | . "$HOME/.mavenrc" 48 | fi 49 | 50 | fi 51 | 52 | # OS specific support. $var _must_ be set to either true or false. 53 | cygwin=false; 54 | darwin=false; 55 | mingw=false 56 | case "$(uname)" in 57 | CYGWIN*) cygwin=true ;; 58 | MINGW*) mingw=true;; 59 | Darwin*) darwin=true 60 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 61 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 62 | if [ -z "$JAVA_HOME" ]; then 63 | if [ -x "/usr/libexec/java_home" ]; then 64 | JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME 65 | else 66 | JAVA_HOME="/Library/Java/Home"; export JAVA_HOME 67 | fi 68 | fi 69 | ;; 70 | esac 71 | 72 | if [ -z "$JAVA_HOME" ] ; then 73 | if [ -r /etc/gentoo-release ] ; then 74 | JAVA_HOME=$(java-config --jre-home) 75 | fi 76 | fi 77 | 78 | # For Cygwin, ensure paths are in UNIX format before anything is touched 79 | if $cygwin ; then 80 | [ -n "$JAVA_HOME" ] && 81 | JAVA_HOME=$(cygpath --unix "$JAVA_HOME") 82 | [ -n "$CLASSPATH" ] && 83 | CLASSPATH=$(cygpath --path --unix "$CLASSPATH") 84 | fi 85 | 86 | # For Mingw, ensure paths are in UNIX format before anything is touched 87 | if $mingw ; then 88 | [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && 89 | JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" 90 | fi 91 | 92 | if [ -z "$JAVA_HOME" ]; then 93 | javaExecutable="$(which javac)" 94 | if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then 95 | # readlink(1) is not available as standard on Solaris 10. 96 | readLink=$(which readlink) 97 | if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then 98 | if $darwin ; then 99 | javaHome="$(dirname "\"$javaExecutable\"")" 100 | javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" 101 | else 102 | javaExecutable="$(readlink -f "\"$javaExecutable\"")" 103 | fi 104 | javaHome="$(dirname "\"$javaExecutable\"")" 105 | javaHome=$(expr "$javaHome" : '\(.*\)/bin') 106 | JAVA_HOME="$javaHome" 107 | export JAVA_HOME 108 | fi 109 | fi 110 | fi 111 | 112 | if [ -z "$JAVACMD" ] ; then 113 | if [ -n "$JAVA_HOME" ] ; then 114 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 115 | # IBM's JDK on AIX uses strange locations for the executables 116 | JAVACMD="$JAVA_HOME/jre/sh/java" 117 | else 118 | JAVACMD="$JAVA_HOME/bin/java" 119 | fi 120 | else 121 | JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" 122 | fi 123 | fi 124 | 125 | if [ ! -x "$JAVACMD" ] ; then 126 | echo "Error: JAVA_HOME is not defined correctly." >&2 127 | echo " We cannot execute $JAVACMD" >&2 128 | exit 1 129 | fi 130 | 131 | if [ -z "$JAVA_HOME" ] ; then 132 | echo "Warning: JAVA_HOME environment variable is not set." 133 | fi 134 | 135 | # traverses directory structure from process work directory to filesystem root 136 | # first directory with .mvn subdirectory is considered project base directory 137 | find_maven_basedir() { 138 | if [ -z "$1" ] 139 | then 140 | echo "Path not specified to find_maven_basedir" 141 | return 1 142 | fi 143 | 144 | basedir="$1" 145 | wdir="$1" 146 | while [ "$wdir" != '/' ] ; do 147 | if [ -d "$wdir"/.mvn ] ; then 148 | basedir=$wdir 149 | break 150 | fi 151 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 152 | if [ -d "${wdir}" ]; then 153 | wdir=$(cd "$wdir/.." || exit 1; pwd) 154 | fi 155 | # end of workaround 156 | done 157 | printf '%s' "$(cd "$basedir" || exit 1; pwd)" 158 | } 159 | 160 | # concatenates all lines of a file 161 | concat_lines() { 162 | if [ -f "$1" ]; then 163 | # Remove \r in case we run on Windows within Git Bash 164 | # and check out the repository with auto CRLF management 165 | # enabled. Otherwise, we may read lines that are delimited with 166 | # \r\n and produce $'-Xarg\r' rather than -Xarg due to word 167 | # splitting rules. 168 | tr -s '\r\n' ' ' < "$1" 169 | fi 170 | } 171 | 172 | log() { 173 | if [ "$MVNW_VERBOSE" = true ]; then 174 | printf '%s\n' "$1" 175 | fi 176 | } 177 | 178 | BASE_DIR=$(find_maven_basedir "$(dirname "$0")") 179 | if [ -z "$BASE_DIR" ]; then 180 | exit 1; 181 | fi 182 | 183 | MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR 184 | log "$MAVEN_PROJECTBASEDIR" 185 | 186 | ########################################################################################## 187 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 188 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 189 | ########################################################################################## 190 | wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" 191 | if [ -r "$wrapperJarPath" ]; then 192 | log "Found $wrapperJarPath" 193 | else 194 | log "Couldn't find $wrapperJarPath, downloading it ..." 195 | 196 | if [ -n "$MVNW_REPOURL" ]; then 197 | wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 198 | else 199 | wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 200 | fi 201 | while IFS="=" read -r key value; do 202 | # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) 203 | safeValue=$(echo "$value" | tr -d '\r') 204 | case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; 205 | esac 206 | done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" 207 | log "Downloading from: $wrapperUrl" 208 | 209 | if $cygwin; then 210 | wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") 211 | fi 212 | 213 | if command -v wget > /dev/null; then 214 | log "Found wget ... using wget" 215 | [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" 216 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 217 | wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 218 | else 219 | wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 220 | fi 221 | elif command -v curl > /dev/null; then 222 | log "Found curl ... using curl" 223 | [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" 224 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 225 | curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" 226 | else 227 | curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" 228 | fi 229 | else 230 | log "Falling back to using Java to download" 231 | javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" 232 | javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" 233 | # For Cygwin, switch paths to Windows format before running javac 234 | if $cygwin; then 235 | javaSource=$(cygpath --path --windows "$javaSource") 236 | javaClass=$(cygpath --path --windows "$javaClass") 237 | fi 238 | if [ -e "$javaSource" ]; then 239 | if [ ! -e "$javaClass" ]; then 240 | log " - Compiling MavenWrapperDownloader.java ..." 241 | ("$JAVA_HOME/bin/javac" "$javaSource") 242 | fi 243 | if [ -e "$javaClass" ]; then 244 | log " - Running MavenWrapperDownloader.java ..." 245 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" 246 | fi 247 | fi 248 | fi 249 | fi 250 | ########################################################################################## 251 | # End of extension 252 | ########################################################################################## 253 | 254 | # If specified, validate the SHA-256 sum of the Maven wrapper jar file 255 | wrapperSha256Sum="" 256 | while IFS="=" read -r key value; do 257 | case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; 258 | esac 259 | done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" 260 | if [ -n "$wrapperSha256Sum" ]; then 261 | wrapperSha256Result=false 262 | if command -v sha256sum > /dev/null; then 263 | if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then 264 | wrapperSha256Result=true 265 | fi 266 | elif command -v shasum > /dev/null; then 267 | if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then 268 | wrapperSha256Result=true 269 | fi 270 | else 271 | echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." 272 | echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." 273 | exit 1 274 | fi 275 | if [ $wrapperSha256Result = false ]; then 276 | echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 277 | echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 278 | echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 279 | exit 1 280 | fi 281 | fi 282 | 283 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 284 | 285 | # For Cygwin, switch paths to Windows format before running java 286 | if $cygwin; then 287 | [ -n "$JAVA_HOME" ] && 288 | JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") 289 | [ -n "$CLASSPATH" ] && 290 | CLASSPATH=$(cygpath --path --windows "$CLASSPATH") 291 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 292 | MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") 293 | fi 294 | 295 | # Provide a "standardized" way to retrieve the CLI args that will 296 | # work with both Windows and non-Windows executions. 297 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" 298 | export MAVEN_CMD_LINE_ARGS 299 | 300 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 301 | 302 | # shellcheck disable=SC2086 # safe args 303 | exec "$JAVACMD" \ 304 | $MAVEN_OPTS \ 305 | $MAVEN_DEBUG_OPTS \ 306 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 307 | "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 308 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 309 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Apache Maven Wrapper startup batch script, version 3.2.0 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 28 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 29 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 30 | @REM e.g. to debug Maven itself, use 31 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 32 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 33 | @REM ---------------------------------------------------------------------------- 34 | 35 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 36 | @echo off 37 | @REM set title of command window 38 | title %0 39 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 40 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 41 | 42 | @REM set %HOME% to equivalent of $HOME 43 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 44 | 45 | @REM Execute a user defined script before this one 46 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 47 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 48 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 49 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* 50 | :skipRcPre 51 | 52 | @setlocal 53 | 54 | set ERROR_CODE=0 55 | 56 | @REM To isolate internal variables from possible post scripts, we use another setlocal 57 | @setlocal 58 | 59 | @REM ==== START VALIDATION ==== 60 | if not "%JAVA_HOME%" == "" goto OkJHome 61 | 62 | echo. 63 | echo Error: JAVA_HOME not found in your environment. >&2 64 | echo Please set the JAVA_HOME variable in your environment to match the >&2 65 | echo location of your Java installation. >&2 66 | echo. 67 | goto error 68 | 69 | :OkJHome 70 | if exist "%JAVA_HOME%\bin\java.exe" goto init 71 | 72 | echo. 73 | echo Error: JAVA_HOME is set to an invalid directory. >&2 74 | echo JAVA_HOME = "%JAVA_HOME%" >&2 75 | echo Please set the JAVA_HOME variable in your environment to match the >&2 76 | echo location of your Java installation. >&2 77 | echo. 78 | goto error 79 | 80 | @REM ==== END VALIDATION ==== 81 | 82 | :init 83 | 84 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 85 | @REM Fallback to current working directory if not found. 86 | 87 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 88 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 89 | 90 | set EXEC_DIR=%CD% 91 | set WDIR=%EXEC_DIR% 92 | :findBaseDir 93 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 94 | cd .. 95 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 96 | set WDIR=%CD% 97 | goto findBaseDir 98 | 99 | :baseDirFound 100 | set MAVEN_PROJECTBASEDIR=%WDIR% 101 | cd "%EXEC_DIR%" 102 | goto endDetectBaseDir 103 | 104 | :baseDirNotFound 105 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 106 | cd "%EXEC_DIR%" 107 | 108 | :endDetectBaseDir 109 | 110 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 111 | 112 | @setlocal EnableExtensions EnableDelayedExpansion 113 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 114 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 115 | 116 | :endReadAdditionalConfig 117 | 118 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 121 | 122 | set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 123 | 124 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 125 | IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B 126 | ) 127 | 128 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 129 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 130 | if exist %WRAPPER_JAR% ( 131 | if "%MVNW_VERBOSE%" == "true" ( 132 | echo Found %WRAPPER_JAR% 133 | ) 134 | ) else ( 135 | if not "%MVNW_REPOURL%" == "" ( 136 | SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 137 | ) 138 | if "%MVNW_VERBOSE%" == "true" ( 139 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 140 | echo Downloading from: %WRAPPER_URL% 141 | ) 142 | 143 | powershell -Command "&{"^ 144 | "$webclient = new-object System.Net.WebClient;"^ 145 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 146 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 147 | "}"^ 148 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ 149 | "}" 150 | if "%MVNW_VERBOSE%" == "true" ( 151 | echo Finished downloading %WRAPPER_JAR% 152 | ) 153 | ) 154 | @REM End of extension 155 | 156 | @REM If specified, validate the SHA-256 sum of the Maven wrapper jar file 157 | SET WRAPPER_SHA_256_SUM="" 158 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 159 | IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B 160 | ) 161 | IF NOT %WRAPPER_SHA_256_SUM%=="" ( 162 | powershell -Command "&{"^ 163 | "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ 164 | "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ 165 | " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ 166 | " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ 167 | " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ 168 | " exit 1;"^ 169 | "}"^ 170 | "}" 171 | if ERRORLEVEL 1 goto error 172 | ) 173 | 174 | @REM Provide a "standardized" way to retrieve the CLI args that will 175 | @REM work with both Windows and non-Windows executions. 176 | set MAVEN_CMD_LINE_ARGS=%* 177 | 178 | %MAVEN_JAVA_EXE% ^ 179 | %JVM_CONFIG_MAVEN_PROPS% ^ 180 | %MAVEN_OPTS% ^ 181 | %MAVEN_DEBUG_OPTS% ^ 182 | -classpath %WRAPPER_JAR% ^ 183 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 184 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 185 | if ERRORLEVEL 1 goto error 186 | goto end 187 | 188 | :error 189 | set ERROR_CODE=1 190 | 191 | :end 192 | @endlocal & set ERROR_CODE=%ERROR_CODE% 193 | 194 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 195 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 196 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 197 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 198 | :skipRcPost 199 | 200 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 201 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 202 | 203 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 204 | 205 | cmd /C exit /B %ERROR_CODE% 206 | -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | events { 2 | worker_connections 10000; 3 | } 4 | http { 5 | access_log off; 6 | 7 | upstream api { 8 | server spring-api1:8080; 9 | server spring-api2:8080; 10 | } 11 | 12 | server { 13 | listen 9999; 14 | 15 | location / { 16 | proxy_pass http://api; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.1.2 9 | 10 | 11 | com.hugomarques 12 | rinhabackend2023 13 | 0.0.1-SNAPSHOT 14 | rinhabackend2023 15 | Back para o projeto rinha backend 2023 16 | 17 | 21 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter-webflux 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-validation 28 | 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-cache 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-starter-data-jpa 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-starter-data-redis 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-starter-data-r2dbc 45 | 46 | 47 | io.r2dbc 48 | r2dbc-postgresql 49 | 0.8.13.RELEASE 50 | 51 | 52 | org.postgresql 53 | postgresql 54 | runtime 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | org.springframework.boot 63 | spring-boot-maven-plugin 64 | 65 | 66 | org.apache.maven.plugins 67 | maven-compiler-plugin 68 | 69 | 21 70 | 21 71 | 72 | --enable-preview 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /src/main/java/com/hugomarques/rinhabackend2023/CacheConfig.java: -------------------------------------------------------------------------------- 1 | package com.hugomarques.rinhabackend2023; 2 | 3 | import com.hugomarques.rinhabackend2023.pessoas.Pessoa; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.data.redis.connection.RedisConnectionFactory; 7 | import org.springframework.data.redis.core.RedisTemplate; 8 | 9 | @Configuration 10 | public class CacheConfig { 11 | 12 | @Bean 13 | public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { 14 | RedisTemplate template = new RedisTemplate<>(); 15 | template.setConnectionFactory(connectionFactory); 16 | return template; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/hugomarques/rinhabackend2023/LoadDatabase.java: -------------------------------------------------------------------------------- 1 | package com.hugomarques.rinhabackend2023; 2 | 3 | import java.util.List; 4 | import java.util.UUID; 5 | import java.util.function.Function; 6 | 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.boot.CommandLineRunner; 10 | 11 | import com.hugomarques.rinhabackend2023.pessoas.Pessoa; 12 | import com.hugomarques.rinhabackend2023.pessoas.PessoaRepository; 13 | import org.springframework.context.annotation.Bean; 14 | import org.springframework.context.annotation.Configuration; 15 | 16 | @Configuration 17 | public class LoadDatabase { 18 | private static final Logger log = LoggerFactory.getLogger(LoadDatabase.class); 19 | 20 | @Bean 21 | CommandLineRunner initDatabase(PessoaRepository repository) { 22 | 23 | return args -> { 24 | // repository.save(new Pessoa(UUID.randomUUID(), "Frodo Baggins", "thies", "1970-01-01", List.of("C#", "Java"))).doOnNext(item -> log.info("Item: " + item)).subscribe(); 25 | // repository.save(new Pessoa(UUID.randomUUID(), "Bilbo Baggins", "burglar", "1970-01-01", List.of("Java"))).doOnNext(item -> log.info("Item: " + item)).subscribe(); 26 | repository.findAllByTerm("gin").doOnNext(item -> log.info("Item: " + item)).subscribe(); 27 | }; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/hugomarques/rinhabackend2023/RinhaBackend2023Application.java: -------------------------------------------------------------------------------- 1 | package com.hugomarques.rinhabackend2023; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cache.annotation.EnableCaching; 6 | import org.springframework.data.jpa.repository.config.EnableJpaRepositories; 7 | import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories; 8 | import org.springframework.scheduling.annotation.EnableAsync; 9 | 10 | @SpringBootApplication 11 | @EnableCaching 12 | @EnableR2dbcRepositories 13 | public class RinhaBackend2023Application { 14 | 15 | public static void main(String[] args) { 16 | SpringApplication.run(RinhaBackend2023Application.class, args); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/hugomarques/rinhabackend2023/pessoas/Pessoa.java: -------------------------------------------------------------------------------- 1 | package com.hugomarques.rinhabackend2023.pessoas; 2 | 3 | 4 | import org.springframework.data.annotation.Id; 5 | import org.springframework.data.relational.core.mapping.Column; 6 | import org.springframework.data.relational.core.mapping.Table; 7 | 8 | import java.io.Serializable; 9 | import java.util.List; 10 | import java.util.Objects; 11 | import java.util.UUID; 12 | 13 | @Table(name = "pessoas") 14 | public class Pessoa implements Serializable { 15 | 16 | @Id 17 | @Column(value = "id") 18 | private UUID id; 19 | @Column(value = "apelido") 20 | private String apelido; 21 | @Column(value = "nome") 22 | private String nome; 23 | @Column(value = "nascimento") 24 | private String nascimento; 25 | @Column(value = "stack") 26 | private List stack; 27 | 28 | public Pessoa() { 29 | } 30 | 31 | public Pessoa(UUID id, String apelido, String nome, String nascimento, List stack) { 32 | this.id = id; 33 | this.apelido = apelido; 34 | this.nome = nome; 35 | this.nascimento = nascimento; 36 | this.stack = stack; 37 | } 38 | 39 | public UUID getId() { 40 | return id; 41 | } 42 | 43 | public void setId(UUID id) { 44 | this.id = id; 45 | } 46 | 47 | public String getApelido() { 48 | return apelido; 49 | } 50 | 51 | public void setApelido(String apelido) { 52 | this.apelido = apelido; 53 | } 54 | 55 | public String getNome() { 56 | return nome; 57 | } 58 | 59 | public void setNome(String nome) { 60 | this.nome = nome; 61 | } 62 | 63 | public String getNascimento() { 64 | return nascimento; 65 | } 66 | 67 | public void setNascimento(String nascimento) { 68 | this.nascimento = nascimento; 69 | } 70 | 71 | public List getStack() { 72 | return stack; 73 | } 74 | 75 | public void setStack(List stack) { 76 | this.stack = stack; 77 | } 78 | 79 | @Override 80 | public boolean equals(Object o) { 81 | if (this == o) return true; 82 | if (o == null || getClass() != o.getClass()) return false; 83 | Pessoa pessoa = (Pessoa) o; 84 | return Objects.equals(id, pessoa.id); 85 | } 86 | 87 | @Override 88 | public int hashCode() { 89 | return Objects.hash(id); 90 | } 91 | 92 | @Override 93 | public String toString() { 94 | return "Pessoa{" + 95 | "id=" + id + 96 | ", apelido='" + apelido + '\'' + 97 | ", nome='" + nome + '\'' + 98 | ", nascimento='" + nascimento + '\'' + 99 | ", stack=" + stack + 100 | '}'; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/com/hugomarques/rinhabackend2023/pessoas/PessoaController.java: -------------------------------------------------------------------------------- 1 | package com.hugomarques.rinhabackend2023.pessoas; 2 | 3 | import java.util.List; 4 | import java.util.UUID; 5 | 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.cache.annotation.CacheConfig; 8 | import org.springframework.data.redis.core.RedisTemplate; 9 | import org.springframework.http.HttpStatus; 10 | import org.springframework.http.ResponseEntity; 11 | import org.springframework.web.bind.annotation.GetMapping; 12 | import org.springframework.web.bind.annotation.PathVariable; 13 | import org.springframework.web.bind.annotation.PostMapping; 14 | import org.springframework.web.bind.annotation.RequestBody; 15 | import org.springframework.web.bind.annotation.RequestParam; 16 | import org.springframework.web.bind.annotation.RestController; 17 | import reactor.core.publisher.Mono; 18 | 19 | @RestController 20 | @CacheConfig(cacheNames = "PessoasCache") 21 | public class PessoaController { 22 | @Autowired 23 | private PessoaRepository repository; 24 | 25 | @Autowired 26 | private RedisTemplate cache; 27 | 28 | /** 29 | * Returns 201 for success and 401 if there's already a person with that same nickname. 30 | * 400 for invalid requests. 31 | */ 32 | @PostMapping("/pessoas") 33 | public Mono> newPessoa(@RequestBody Pessoa pessoa) { 34 | return Mono.fromCallable( () -> { 35 | if (cache.opsForValue().get(pessoa.getApelido()) != null) { 36 | return ResponseEntity.unprocessableEntity().build(); 37 | } 38 | pessoa.setId(UUID.randomUUID()); 39 | cache.opsForValue().set(pessoa.getApelido(), pessoa); 40 | cache.opsForValue().set(pessoa.getId().toString(), pessoa); 41 | repository.save(pessoa); 42 | return new ResponseEntity(pessoa, HttpStatus.CREATED); 43 | }); 44 | } 45 | 46 | /** 47 | * 200 for OK 48 | * 404 for not found. 49 | */ 50 | @GetMapping("/pessoas/{id}") 51 | public Mono> getById(@PathVariable UUID id) { 52 | Pessoa cached = cache.opsForValue().get(id.toString()); 53 | if (cached != null) { 54 | return Mono.just(ResponseEntity.ok(cached)); 55 | } 56 | return repository.findById(id) 57 | .map(ResponseEntity::ok) 58 | .switchIfEmpty(Mono.error(new PessoaNotFoundException(id))); 59 | } 60 | 61 | @GetMapping("/pessoas") 62 | public Mono>> findAllBySearchTerm(@RequestParam(name = "t") String term) { 63 | if (term == null || term.isEmpty() || term.isBlank()) { 64 | return Mono.just(ResponseEntity.badRequest().build()); 65 | } 66 | return repository.findAllByTerm(term).collectList().map(ResponseEntity::ok); 67 | } 68 | 69 | @GetMapping("/contagem-pessoas") 70 | public Mono> count() { 71 | return repository.count() 72 | .map(count -> ResponseEntity.ok(String.valueOf(count))); 73 | } 74 | 75 | 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/com/hugomarques/rinhabackend2023/pessoas/PessoaErrorsAdvice.java: -------------------------------------------------------------------------------- 1 | package com.hugomarques.rinhabackend2023.pessoas; 2 | 3 | import org.springframework.dao.DataIntegrityViolationException; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.web.bind.annotation.ControllerAdvice; 6 | import org.springframework.web.bind.annotation.ExceptionHandler; 7 | import org.springframework.web.bind.annotation.ResponseBody; 8 | import org.springframework.web.bind.annotation.ResponseStatus; 9 | 10 | @ControllerAdvice 11 | public class PessoaErrorsAdvice { 12 | 13 | @ResponseBody 14 | @ExceptionHandler(PessoaNotFoundException.class) 15 | @ResponseStatus(HttpStatus.NOT_FOUND) 16 | String pessoaNotFoundHandler(PessoaNotFoundException ex) { 17 | return ex.getMessage(); 18 | } 19 | 20 | @ResponseBody 21 | @ExceptionHandler(DataIntegrityViolationException.class) 22 | @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY) 23 | String apelidoDuplicadoHandler(DataIntegrityViolationException ex) { 24 | return "Apelido já em uso"; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/hugomarques/rinhabackend2023/pessoas/PessoaNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.hugomarques.rinhabackend2023.pessoas; 2 | 3 | 4 | import java.util.UUID; 5 | 6 | public class PessoaNotFoundException extends RuntimeException { 7 | PessoaNotFoundException(UUID id) { 8 | super("Pessoa com id: " + id + " não encontrada."); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/hugomarques/rinhabackend2023/pessoas/PessoaRepository.java: -------------------------------------------------------------------------------- 1 | package com.hugomarques.rinhabackend2023.pessoas; 2 | 3 | import org.springframework.data.r2dbc.repository.Query; 4 | import org.springframework.data.r2dbc.repository.R2dbcRepository; 5 | import org.springframework.stereotype.Repository; 6 | import reactor.core.publisher.Flux; 7 | 8 | import java.util.UUID; 9 | 10 | @Repository 11 | public interface PessoaRepository extends R2dbcRepository { 12 | 13 | @Query("SELECT p.id, p.apelido, p.nome, p.nascimento, p.stack FROM pessoas p " + 14 | "WHERE p.apelido LIKE CONCAT('%', :term, '%') " + 15 | "OR p.nome LIKE CONCAT('%', :term, '%') " + 16 | "OR p.nascimento LIKE CONCAT('%', :term, '%') " + 17 | "OR p.stack LIKE CONCAT('%', :term, '%')") 18 | Flux findAllByTerm(String term); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/hugomarques/rinhabackend2023/pessoas/StringListConverter.java: -------------------------------------------------------------------------------- 1 | package com.hugomarques.rinhabackend2023.pessoas; 2 | 3 | import jakarta.persistence.AttributeConverter; 4 | import jakarta.persistence.Converter; 5 | 6 | import java.util.Arrays; 7 | import java.util.List; 8 | 9 | import static java.util.Collections.emptyList; 10 | 11 | @Converter 12 | public class StringListConverter implements AttributeConverter, String> { 13 | private static final String SPLIT_CHAR = ";"; 14 | 15 | @Override 16 | public String convertToDatabaseColumn(List stringList) { 17 | return stringList != null ? String.join(SPLIT_CHAR, stringList) : null; 18 | } 19 | 20 | @Override 21 | public List convertToEntityAttribute(String string) { 22 | return string != null ? Arrays.asList(string.split(SPLIT_CHAR)) : emptyList(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=rinha-backend-2023 2 | spring.main.allow-bean-definition-overriding=true 3 | 4 | # Server 5 | server.port=${SERVER_PORT:8080} 6 | server.compression.enabled=true 7 | server.compression.min-response-size=1024 8 | 9 | # Database 10 | spring.r2dbc.url=${DATABASE_URL:r2dbc:postgresql://localhost:2345/rinhadb} 11 | spring.r2dbc.username=${DATABASE_USERNAME:rinha} 12 | spring.r2dbc.password=${DATABASE_PASSWORD:rinha123} 13 | 14 | ### Redis 15 | spring.cache.type=redis 16 | spring.data.redis.host=${REDIS_HOST:localhost} 17 | spring.data.redis.port=6379 -------------------------------------------------------------------------------- /stress-test/.gitignore: -------------------------------------------------------------------------------- 1 | /user-files/results 2 | -------------------------------------------------------------------------------- /stress-test/README.md: -------------------------------------------------------------------------------- 1 | # Fonte do Teste de Stress 2 | 3 | Teste feito com a ferramenta gatling. -------------------------------------------------------------------------------- /stress-test/geracao_recursos.py: -------------------------------------------------------------------------------- 1 | import random 2 | import json 3 | import string 4 | 5 | def get_stacks_validas(): 6 | stacks = [ 7 | "Javascript" 8 | , "Python" 9 | , "Go" 10 | , "Java" 11 | , "Kotlin" 12 | , "PHP" 13 | , "C#" 14 | , "Swift" 15 | , "R" 16 | , "Ruby" 17 | , "C" 18 | , "C++" 19 | , "Matlab" 20 | , "TypeScript" 21 | , "Scala" 22 | , "SQL" 23 | , "HTML" 24 | , "CSS" 25 | , "NoSQL" 26 | , "Rust" 27 | , "Perl" 28 | , "C#" 29 | , "Clojure" 30 | , "MySQL" 31 | , "Postgres"] 32 | tem_stack = random.choice([i != 11 for i in range(1, 15)]) 33 | valor_stacks = None 34 | if tem_stack: 35 | valor_stacks = random.choices(stacks, k=random.randint(1, len(stacks) + 1)) 36 | return valor_stacks 37 | 38 | def get_apelido_valido(): 39 | return ''.join(random.choice(string.ascii_letters) for i in range(100))[:random.randint(1, 32)] 40 | 41 | def get_nome_valido(): 42 | return ''.join(random.choice(string.ascii_letters + ' ') for i in range(100))[:random.randint(1, 100)] 43 | 44 | def get_nascimento_valido(): 45 | return str(random.randint(1940, 2020)) + '-' + str(random.randint(1, 12+1)).rjust(2, '0') + '-' + str(random.randint(1, 28+1)).rjust(2, '0') 46 | 47 | def get_stacks_invalidas(): 48 | return random.choice([["1111111111111111111111111111111111111111"], "string", 1]) 49 | 50 | def get_nick_invalido(): 51 | return ''.join(random.choice(string.ascii_letters) for i in range(35)) 52 | 53 | def get_nome_invalido(): 54 | return ''.join(random.choice(string.ascii_letters + ' ') for i in range(120)) 55 | 56 | def get_nascimento_invalido(): 57 | return json.dumps(random.choice(["12-12-2000", None, 10, "?!?!?"])) 58 | 59 | def get_payload(): 60 | gera_apelido_valido = random.choice([i != 1 for i in range(1, 100)]) 61 | get_apelido = get_apelido_valido if gera_apelido_valido else get_nick_invalido 62 | 63 | gera_nome_valido = random.choice([i != 1 for i in range(1, 100)]) 64 | get_nome = get_nome_valido if gera_nome_valido else get_nome_invalido 65 | 66 | gera_nascimento_valido = random.choice([i != 1 for i in range(1, 100)]) 67 | get_nascimento = get_nascimento_valido if gera_nascimento_valido else get_nascimento_invalido 68 | 69 | gera_stacks_validas = random.choice([i != 1 for i in range(1, 100)]) 70 | get_stacks = get_stacks_validas if gera_stacks_validas else get_stacks_invalidas 71 | 72 | payload = {"apelido" : get_apelido(), "nome" : get_nome(), "nascimento" : get_nascimento(), "stack" : get_stacks()} 73 | return json.dumps(payload) 74 | 75 | def get_termo_busca(): 76 | t = ''.join(random.choice(string.ascii_letters + ' ') for i in range(100))[:random.randint(1, 50)] 77 | return t.strip() or 'x' 78 | 79 | def gera_payloads(numero_registros): 80 | with open("user-files/resources/pessoas-payloads.tsv", "w") as f: 81 | f.write("payload\n") 82 | for _ in range(numero_registros): 83 | f.write(f"{get_payload()}\n") 84 | 85 | def gera_termos_busca(numero_registros): 86 | with open("user-files/resources/termos-busca.tsv", "w") as f: 87 | f.write("t\n") 88 | for _ in range(numero_registros): 89 | f.write(get_termo_busca() + "\n") 90 | 91 | gera_payloads(100_000) 92 | gera_termos_busca(5_000) -------------------------------------------------------------------------------- /stress-test/run-test-aws.sh: -------------------------------------------------------------------------------- 1 | # Exemplos de requests 2 | # curl -v -XPOST -H "content-type: application/json" -d '{"apelido" : "xpto", "nome" : "xpto xpto", "nascimento" : "2000-01-01", "stack": null}' "http://localhost:9999/pessoas" 3 | # curl -v -XGET "http://localhost:9999/pessoas/1" 4 | # curl -v -XGET "http://localhost:9999/pessoas?t=xpto" 5 | # curl -v "http://localhost:9999/contagem-pessoas" 6 | 7 | GATLING_BIN_DIR=$HOME/gatling/bin 8 | 9 | WORKSPACE=$HOME/rinha-de-backend-2023-q3/stress-test 10 | 11 | sh $GATLING_BIN_DIR/gatling.sh -rm local -s RinhaBackendSimulation \ 12 | -rd "DESCRICAO" \ 13 | -rf $WORKSPACE/user-files/results \ 14 | -sf $WORKSPACE/user-files/simulations \ 15 | -rsf $WORKSPACE/user-files/resources \ 16 | -------------------------------------------------------------------------------- /stress-test/run-test.sh: -------------------------------------------------------------------------------- 1 | # Exemplos de requests 2 | # curl -v -XPOST -H "content-type: application/json" -d '{"apelido" : "xpto", "nome" : "xpto xpto", "nascimento" : "2000-01-01", "stack": null}' "http://localhost:9999/pessoas" 3 | # curl -v -XGET "http://localhost:9999/pessoas/1" 4 | # curl -v -XGET "http://localhost:9999/pessoas?t=xpto" 5 | # curl -v "http://localhost:9999/contagem-pessoas" 6 | 7 | GATLING_BIN_DIR=$HOME/gatling/3.9.5/bin 8 | 9 | WORKSPACE=$HOME/workspace/rinha-de-backend-2023-q3/stress-test 10 | 11 | sh $GATLING_BIN_DIR/gatling.sh -rm local -s RinhaBackendSimulation \ 12 | -rd "DESCRICAO" \ 13 | -rf $WORKSPACE/user-files/results \ 14 | -sf $WORKSPACE/user-files/simulations \ 15 | -rsf $WORKSPACE/user-files/resources \ 16 | 17 | <<<<<<< Updated upstream 18 | sleep 3 19 | 20 | curl -v "http://localhost:9999/contagem-pessoas" 21 | ======= 22 | curl -v "http://localhost:9999/contagem-pessoas" 23 | >>>>>>> Stashed changes 24 | -------------------------------------------------------------------------------- /stress-test/user-files/simulations/rinhabackend/RinhaBackendSimulation.scala: -------------------------------------------------------------------------------- 1 | import scala.concurrent.duration._ 2 | 3 | import scala.util.Random 4 | 5 | import io.gatling.core.Predef._ 6 | import io.gatling.http.Predef._ 7 | 8 | 9 | class RinhaBackendSimulation 10 | extends Simulation { 11 | 12 | val httpProtocol = http 13 | .baseUrl("http://localhost:9999") 14 | .userAgentHeader("Agente do Caos - 2023") 15 | 16 | val criacaoEConsultaPessoas = scenario("Criação E Talvez Consulta de Pessoas") 17 | .feed(tsv("pessoas-payloads.tsv").circular()) 18 | .exec( 19 | http("criação") 20 | .post("/pessoas").body(StringBody("#{payload}")) 21 | .header("content-type", "application/json") 22 | // 201 pros casos de sucesso :) 23 | // 422 pra requests inválidos :| 24 | // 400 pra requests bosta tipo data errada, tipos errados, etc. :( 25 | .check(status.in(201, 422, 400)) 26 | .check(header("Location").optional.saveAs("location")) 27 | ) 28 | .pause(1.milliseconds, 30.milliseconds) 29 | .doIf(session => session.contains("location")) { 30 | exec( 31 | http("consulta") 32 | .get("#{location}") 33 | ) 34 | } 35 | 36 | val buscaPessoas = scenario("Busca Válida de Pessoas") 37 | .feed(tsv("termos-busca.tsv").circular()) 38 | .exec( 39 | http("busca válida") 40 | .get("/pessoas?t=#{t}") 41 | // qq resposta na faixa 2XX tá safe 42 | ) 43 | 44 | val buscaInvalidaPessoas = scenario("Busca Inválida de Pessoas") 45 | .exec( 46 | http("busca inválida") 47 | .get("/pessoas") 48 | // 400 - bad request se não passar 't' como query string 49 | .check(status.is(400)) 50 | ) 51 | 52 | setUp( 53 | criacaoEConsultaPessoas.inject( 54 | constantUsersPerSec(2).during(10.seconds), // warm up 55 | constantUsersPerSec(5).during(15.seconds).randomized, // are you ready? 56 | 57 | rampUsersPerSec(6).to(300).during(3.minutes) // lezzz go!!! 58 | ), 59 | buscaPessoas.inject( 60 | constantUsersPerSec(2).during(25.seconds), // warm up 61 | 62 | rampUsersPerSec(6).to(50).during(3.minutes) // lezzz go!!! 63 | ), 64 | buscaInvalidaPessoas.inject( 65 | constantUsersPerSec(2).during(25.seconds), // warm up 66 | 67 | rampUsersPerSec(6).to(20).during(3.minutes) // lezzz go!!! 68 | ) 69 | ).protocols(httpProtocol) 70 | } --------------------------------------------------------------------------------