きっかけ
Athena便利ですよね。
S3にCSV形式とかのファイルを置いて、Glue crawler実行するだけで集計できるようになっちゃうんですよ。
ディレクトリを日付ごとに分けることで、パーティションも自由自在!
こんなに簡単にログ集計できていいんですかね。
でもでも、取得したデータをちょこっと複雑なビジネスロジックで変換して出力したいこと…ありますよね。
あー、Kotlinで処理が書けたらなぁ…
というわけで、SpringでAthenaを使おう!
使おう
今回のコードはここにあります。
そして、コードは、ここを参考にしました。
なんでもブログにあって、クラメソ殿しゅごい。
SpringBootを使ってAWS Athenaへ接続してみる | DevelopersIO
でも少し書き換えが必要
でも、ちょっと前の記事なので書き直しが必要なところがあるのですよね。
ここのJDBC移行ガイドある通り、JDBC Driverのクラス名が変わったのですよね。
JDBC ドライバーを介した Athena の使用 - Amazon Athena
旧 -> com.amazonaws.athena.jdbc.AthenaDriver
新 -> com.simba.athena.jdbc.Driver
なので、application.ymlはこんな感じになります。
spring: datasource: driver-class-name: com.simba.athena.jdbc.Driver url: jdbc:awsathena://athena.ap-northeast-1.amazonaws.com:443;s3_staging_dir=s3://takeda-athena-results athena: database: takedaathena
URLは jdbc:awsathena://AwsRegion={REGION}
みたいな感じでも書けるみたいですね。
こっちのほうが短くて簡潔かな。
URLにパラメータの指定をするときの記号も
旧 -> &
and ?
新 -> ;
に変わってるみたいです。
便利で便利なDefaultAWSCredentialsProviderChain
クラメソ殿の記事だと認証まわりにProfileCredentialsProvider
を使っていて、ローカルではPorfile、本番ではRoleを使ったりのように切り替えたいときに、ちょっと困る。
こんなときにDefaultAWSCredentialsProviderChain
を指定するとよしなにやってくれるので、非常に便利でした。
この記事読んでマ!ってなりました。
AWS SDK for Java がデフォルトで参照する credential - Qiita
優先順に認証情報を探して、適用してくれるのです。
なので、Java Configはこんな感じになってます。
@Configuration class AthenaConfig { @Bean @Primary @ConfigurationProperties(prefix = "spring.datasource") fun dataSourceProperties() = DataSourceProperties() @Bean("dataSource") @Primary @ConfigurationProperties(prefix = "spring.datasource") fun dataSource(): SimpleDriverDataSource { val settings = dataSourceProperties() val dataSource = SimpleDriverDataSource() dataSource.apply { @Suppress("UNCHECKED_CAST") setDriverClass(Class.forName(settings.determineDriverClassName()) as Class<out Driver>) url = settings.determineUrl() // 認証設定を追加する val connectionProperties = Properties() connectionProperties.setProperty( "aws_credentials_provider_class", "com.simba.athena.amazonaws.auth.DefaultAWSCredentialsProviderChain" ) setConnectionProperties(connectionProperties) } return dataSource } @Bean("jdbcTemplate") fun jdbcTemplate( @Autowired @Qualifier("dataSource") dataSource: SimpleDriverDataSource ): JdbcTemplate = JdbcTemplate(dataSource) }
ここ、なんか汚くて怒られそうですが
@Suppress("UNCHECKED_CAST") setDriverClass(Class.forName(settings.determineDriverClassName()) as Class<out Driver>) url = settings.determineUrl()
普通のSpringのDataSourceのままだと、好きなプロパティが差し込めないのでSimpleDataSourceを使ってDefaultAWSCredentialsProviderChain
を差し込んでます。
うーん、専用のクラス作ったほうが早いかな。
クエリの発行
無事設定も終わったので、JdbcTemplateを使ってクエリを発行します。
fun selectSomeByDate(date: LocalDate): List<Person> { val sql = """ SELECT name, age FROM $database.some WHERE some.year = '${date.year}' AND some.month = '${date.monthValue.toString().padStart(2, '0')}' AND some.day = '${date.dayOfMonth.toString().padStart(2, '0')}' ; """.trimIndent() return jdbcTemplate.query(sql) { resultSet, _ -> Person( name = resultSet.getString("name"), age = resultSet.getInt("age") ) } }
SQLライクで使いやすいですねぇ。
結果
Person(name=hoge, age=11)
やったね。
これでログ集計し放題!
困ること
無事、Springの世界にデータを持ってこれたのですが、実際の本番ログとかって量が膨大でメモリ上に載りきらないことがありそうですね。
こんな場合ってどうするんでしょ。
Athenaで引っ張ってきたデータを全部リストに格納したいとかってありそうですが…?
なんか随時ファイルに書き込んでメモリ上から消してくとかあるんですかねぇ。