피더(Feeder)로 세션에 데이터 주입하기
피더(Feeder)로 세션에 데이터 주입하기
부하 테스트에서 가상 사용자마다 서로 다른 입력을 넣어야 할 때가 많죠. 예를 들어 로그인 테스트라면 사용자마다 다른 계정 정보를 써야 해요. 이때 외부 소스(가령 CSV 파일)에서 데이터를 꺼내 가상 사용자에게 주입하는 게 바로 피더(feeder)예요.
피더는 가상 사용자가 소비할 수 있는 데이터 '재고(stock)'라고 볼 수 있어요. SDK는 exec와 같은 자리에 부를 수 있는 feed 메서드를 제공해요. 이 글에서는 피더의 기본 사용법과 다양한 소스, 소비 전략을 다룰게요.
본문
기본 사용법
feed로 정의된 단계에서는 모든 가상 사용자가 같은 피더에서 데이터를 꺼내 써요. 가상 사용자가 이 단계에 도달할 때마다 피더에서 레코드 하나를 받고, 그 레코드는 사용자의 세션에 주입되어 새 속성으로 사용할 수 있게 돼요.
// 단독으로 호출
feed(feeder)
// 시나리오나 exec에 붙이기
scenario("scn").feed(feeder)
여러 레코드를 한 번에 주입할 수도 있어요. 이 경우 같은 키의 값들을 모두 담은 Seq가 세션 속성이 돼요.
// 한 번에 2개 레코드 주입
feed(feeder, 2)
// 세션 속성으로 정의된 개수만큼 주입
feed(feeder, "#{numberOfRecords}")
// 함수로 세션에서 동적으로 계산
feed(feeder, session => session("numberOfRecords").as[Int])
인메모리 배열·리스트 피더
파일 없이 메모리에 있는 자료구조를 피더로 쓸 수도 있어요.
// 배열 기반 (암시적 변환)
Array(
Map("foo" -> "foo1", "bar" -> "bar1"),
Map("foo" -> "foo2", "bar" -> "bar2"),
Map("foo" -> "foo3", "bar" -> "bar3")
)
// IndexedSeq 기반 (암시적 변환)
IndexedSeq(
Map("foo" -> "foo1", "bar" -> "bar1"),
Map("foo" -> "foo2", "bar" -> "bar2"),
Map("foo" -> "foo3", "bar" -> "bar3")
)
파일 기반 피더
Java·Kotlin·Scala에서는 파일을 src/main/resources나 src/test/resources(Gradle이라면 src/gatling/resources)에 두고, 그 루트부터의 **상대 경로(classpath 경로)**를 설정해야 해요. data/file.csv처럼 상대적인 파일시스템 경로 대신 classpath 경로를 쓰세요. 별도 배포를 원한다면 절대 경로로도 설정할 수 있어요.
CSV 피더
컴마로 구분된 값(CSV)을 읽는 내장 피더가 여러 개 있어요. 파서는 RFC4180 명세를 따르며, 유일한 차이는 헤더 필드의 앞뒤 공백을 제거한다는 점이에요.
// 기본 전략 — 이어지는 "전략" 참고
csv("data/file.csv").queue
Sitemap 피더
Sitemap 파일에서 데이터를 읽는 피더도 지원해요.
// http 모듈 import 필요
import io.gatling.http.Predef._
sitemap("/path/to/sitemap/file")
JDBC 피더
JDBC 커넥션에서 데이터를 읽는 내장 피더도 있어요. 파일 파서와 마찬가지로 RecordSeqFeederBuilder 인스턴스를 반환해요.
// jdbc 모듈 import 필요
import io.gatling.jdbc.Predef._
jdbcFeeder("databaseUrl", "username", "password", "SELECT * FROM users")
databaseUrl은 JDBC URL이어야 해요 (예:jdbc:postgresql:gatling).username·password는 DB 접속 자격 증명이에요.sql은 필요한 값을 얻는 쿼리예요.- JDBC4 드라이버만 지원돼서 DriverManager에 자동 등록돼요.
Redis 피더
Gatling은 Redis에서도 데이터를 읽을 수 있어요. 원래 Krishnen Chedambarum이 기여한 기능이에요.
// redis 모듈 import 필요
import io.gatling.redis.Predef._
val redisPool = RedisClientPool("localhost", 6379)
// 리스트를 사용해 레코드당 값 하나, 이름은 "foo"
redisFeeder(redisPool, "foo")
Redis 명령을 재정의할 수도 있어요. 같은 키를 RPOPLPUSH에 쓰면 원형(circular) 피더를 만들 수 있어요.
// "foo"라는 set에서 SPOP 명령으로 읽기
redisFeeder(redisPool, "foo").SPOP
// "foo"라는 set에서 SRANDMEMBER 명령으로 읽기
redisFeeder(redisPool, "foo").SRANDMEMBER
// "foo" 리스트에서 "bar"로 원자적으로 옮기며 읽기
redisFeeder(redisPool, "foo", "bar").RPOPLPUSH
// 같은 키를 쓰면 원형 리스트가 됨
redisFeeder(redisPool, "foo", "foo").RPOPLPUSH
전략(Strategies)
내장 피더에는 여러 소비 전략이 있어요.
queue
기본 전략으로, 따로 지정하지 않으면 적용돼요. queue 전략에서는 가상 사용자가 레코드를 '소비'하므로:
- 어떤 가상 사용자도 같은 레코드를 받지 못해요.
- 어느 시점엔 데이터 재고가 전부 소진돼요.
여러 가상 사용자가 같은 레코드(예: 같은 자격 증명)를 쓰면 중복이 생기면 안 되는 경우나, CSV에 정의된 순서 그대로 소비해야 하는 경우에 적합해요.
// 기본 동작 — 생략 가능
csv("foo").queue
예를 들어 CSV가 아래와 같다면:
key
1
2
3
queue 전략에서는 1번째 가상 사용자가 ("key","1"), 2번째가 ("key","2"), 3번째가 ("key","3")을 소비하고, 4번째 가상 사용자는 Gatling이 크래시해요.
shuffle
queue와 매우 비슷하지만 레코드를 무작위 순서로 소비한다는 점만 달라요.
커스텀 피더
피더는 Iterator<Map<String, T>>의 타입 별칭이에요. feed가 만든 컴포넌트가 이 Map 레코드를 폴링해 내용을 주입해요. 커스텀 피더는 매우 쉽게 만들 수 있어요. 예를 들어 무작위 이메일 생성기는 이렇게 만들어요.
import scala.util.Random
val feeder = Iterator.continually {
Map("email" -> s"${Random.alphanumeric.take(20).mkString}@foo.com")
}