Multi-tenant 로 MariaDB 검색 예제를 만들어보겠습니다.
1) Multi-tenant 설계
회사에서 개발을 할 때 Environment 를 Live, Stage, Dev 로 나누는 경우가 많습니다.
✅ 1.1 Dev (Development)
개발 환경
- 개발자들이 새로운 기능을 개발하거나 테스트하는 환경
- 버그가 많을 수 있음 (개발 중이기 때문에)
- 실제 데이터가 아닌 모의 데이터(mock data) 사용
- 자주 수정되고 재시작됨
🧪 예시:
"로그인 기능 새로 개발했는데 dev 환경에 올려볼게."
✅ 1.2 Stage (Staging)
사전 검증 환경 (실제 배포 전 테스트용)
- 운영 환경과 거의 동일한 구성 (서버, DB, 설정 등)
- QA 팀이 기능을 점검하거나 PM이 검토할 때 사용
- 실제 데이터와 유사한 테스트 데이터 사용
- 릴리스 전 최종 점검 단계
🧪 예시:
"이번 배포 전에 stage 환경에서 한번 더 테스트해보자."
✅ 1.3 Live (Production)
실제 서비스 운영 환경
- 유저들이 직접 사용하는 실제 서비스
- 장애 발생 시 비즈니스에 바로 영향을 줌 ⚠️
- 코드와 데이터 모두 신중히 다뤄야 함
- 실시간 트래픽, 진짜 사용자 데이터
그리고 각 Environment 를 first, second, ... 와 같이 db_type 으로 나누기도 합니다.
이는 각 고객이나 서비스를 type 별로 구별하여 좀더 세분화 하기 위해서 입니다.
이를 scale out 이라고 합니다.
scale out 이 scale up 보다 장점이 많이 존재합니다.
설명 | |
Scale-Up | 더 좋은 서버로 교체 → CPU, 메모리, 디스크 용량을 늘리는 방식 |
Scale-Out | 서버 개수를 늘려 부하를 분산하는 방식 (서버 여러 대 운영) |
✅ db_type으로 나누는 Scale-Out의 장점
항목 | 설명 |
💥 성능 분산 | 예: first는 고객 주문 DB, second는 상품 관리 DB → 각각의 부하가 분리됨 → 하나가 느려도 다른 DB에는 영향 없음 |
🛠 유지보수 유연성 | 특정 DB만 백업/복구, 튜닝, 재시작 가능 → 전체 다운 없이 부분만 점검 가능 |
🚀 수평 확장성 | VM, 컨테이너 등 개별 인스턴스를 필요에 따라 더 추가하기 쉬움 |
🔐 보안/권한 분리 | 업무 또는 서비스별로 DB 접근 권한을 다르게 설정할 수 있음 |
🧪 테스트 분리 용이 | 개발자가 특정 db_type 만 테스트하거나 복제해서 실험 가능 |
💸 비용 최적화 | 필요한 DB만 리소스를 늘릴 수 있어, 전체 고성능 서버보다 비용 효율적 |
❌ Scale-Up 방식의 한계
한계 | 설명 |
단일 장애 지점 (SPOF) | 모든 DB가 하나의 인스턴스에 있으면, 다운 시 전체 서비스 영향 |
리소스 한계 | 아무리 좋은 서버라도 CPU, RAM은 물리적 한계가 있음 |
운영 부담 증가 | 하나의 DB에 모든 기능이 몰리면 관리/튜닝이 어려워짐 |
🎯 예시: multi-tenant + scale-out 구조
예를 들면 아래와 같이 first DB 는 주문 처리를 하고 second DB 는 상품 관리를 하는 것과 같이 분리할 수 있습니다.
다음은 전체 설계 내용입니다.
Environment 로 Lvie(상용 서버 배포), Stage(상용 배포전 사전 검증), Dev(개발 서버) 로 구분하였습니다.
db_type 으로 first, second 를 분리하였습니다.
ChatGPT 에 사용한 프롬프트입니다.
vagrant 로 rocky 9 OS에 mariadb VM 6개를 만들려고 합니다.
mariadb 는 password vagrant 로 접속 가능하게 해주세요.
외부에서 3306 포트로 접속이 가능하게 해주세요.
sample db 도 만들고 sample table 도 생성하고 sample data 도 추가해주세요.
multi-tenant 로 environment, db_type 으로 구분하려고 합니다.
environment 는 Live, Stage, Dev 로 나눌 예정이며
db_type 은 first, second 로 나눌 예정입니다.
VM 이름과 IP 는 다음과 같습니다.
dev_first : 192.168.56.20
dev_second : 192.168.56.21
stage_first : 192.168.56.22
stage_second : 192.168.56.23
live_first : 192.168.56.24
live_second : 192.168.56.25
python 으로 environment, db_type 이라는 파라미터 입력을 받아서
각 VM 에 설치된 mariadb 의 sample table 을 검색하는 예제를 만들어주세요.
first 에는 sample table 로 주문 처리 관련 테이블을 생성하고 sample 데이터도 넣어주세요.
second 에는 sample table 로 상품 관리 테이블을 생성하고 sample 데이터도 넣어주세요.
생성된 Vagrantfile 입니다.
아래 파일은 다음 git 에서 다운로드 가능합니다.
git clone git@github.com:jbpark/jbDeskExample.git
cd jbDeskExample/jbDesk/ch2.3
✅ 1) VM 구성 (Vagrantfile 예시)
VAGRANT_BOX = "generic/rocky9"
DB_PASSWORD = "vagrant"
MARIADB_PORT = 3306
VMs = {
"dev-first" => "192.168.56.20",
"dev-second" => "192.168.56.21",
"stage-first" => "192.168.56.22",
"stage-second" => "192.168.56.23",
"live-first" => "192.168.56.24",
"live-second" => "192.168.56.25"
}
Vagrant.configure("2") do |config|
config.vm.box = VAGRANT_BOX
VMs.each do |name, ip|
config.vm.define name do |vm_config|
vm_config.vm.hostname = name
vm_config.vm.network "private_network", ip: ip
vm_config.vm.provider "virtualbox" do |vb|
vb.name = name
vb.memory = 1024
vb.cpus = 1
end
# Determine table type based on VM name
table_sql = if name.include?("first")
<<-SQL
CREATE TABLE IF NOT EXISTS orders (
id INT AUTO_INCREMENT PRIMARY KEY,
customer_name VARCHAR(100),
product VARCHAR(100),
quantity INT,
order_date DATE
);
INSERT IGNORE INTO orders (customer_name, product, quantity, order_date)
VALUES ('John Doe', 'Laptop', 1, '2025-04-01'),
('Jane Smith', 'Monitor', 2, '2025-04-02');
SQL
else
<<-SQL
CREATE TABLE IF NOT EXISTS products (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100),
price INT,
stock INT
);
INSERT IGNORE INTO products (name, price, stock)
VALUES ('Laptop', 1200000, 10),
('Monitor', 300000, 25);
SQL
end
vm_config.vm.provision "shell", inline: <<-SHELL
dnf install -y mariadb-server
systemctl enable mariadb
systemctl start mariadb
echo "[+] Configuring MariaDB to allow remote access..."
# bind-address=0.0.0.0 설정
sed -i 's/^bind-address=127.0.0.1/bind-address=0.0.0.0/' /etc/my.cnf.d/mariadb-server.cnf || \
echo -e "[mysqld]\nbind-address=0.0.0.0" >> /etc/my.cnf.d/mariadb-server.cnf
systemctl restart mariadb
echo "[+] Creating remote-accessible root user..."
mariadb -e "GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY 'vagrant' WITH GRANT OPTION;"
mariadb -e "FLUSH PRIVILEGES;"
# Create sample DB and appropriate table
mysql -uroot -p#{DB_PASSWORD} <<EOF
CREATE DATABASE IF NOT EXISTS sample;
USE sample;
#{table_sql}
EOF
echo "[+] Opening firewall for MariaDB..."
firewall-cmd --permanent --add-port=3306/tcp
firewall-cmd --reload
SHELL
end
end
end
📝 실행 방법
vagrant up
✅ 2) Python 예제 (query_sample_table.py)
import pymysql
# VM 정보 매핑
VM_INFO = {
"dev_first": {"ip": "192.168.56.20"},
"dev_second": {"ip": "192.168.56.21"},
"stage_first": {"ip": "192.168.56.22"},
"stage_second": {"ip": "192.168.56.23"},
"live_first": {"ip": "192.168.56.24"},
"live_second": {"ip": "192.168.56.25"},
}
def get_vm_name(environment: str, db_type: str) -> str:
return f"{environment.lower()}_{db_type.lower()}"
def table_exists(cursor, table_name: str) -> bool:
cursor.execute(f"SHOW TABLES LIKE '{table_name}';")
return cursor.fetchone() is not None
def query_sample_tables(ip: str, user: str = "root", password: str = "vagrant", db: str = "sample"):
try:
conn = pymysql.connect(host=ip, port=3306, user=user, password=password, database=db)
cursor = conn.cursor()
if table_exists(cursor, "orders"):
print("[orders 테이블]")
cursor.execute("SELECT * FROM orders;")
for row in cursor.fetchall():
print(row)
else:
print("[orders 테이블 없음]")
if table_exists(cursor, "products"):
print("\n[products 테이블]")
cursor.execute("SELECT * FROM products;")
for row in cursor.fetchall():
print(row)
else:
print("[products 테이블 없음]")
cursor.close()
conn.close()
except Exception as e:
print(f"[!] 접속 실패: {e}")
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--environment", choices=["dev", "stage", "live"], required=True)
parser.add_argument("--db_type", choices=["first", "second"], required=True)
args = parser.parse_args()
vm_name = get_vm_name(args.environment, args.db_type)
ip = VM_INFO.get(vm_name, {}).get("ip")
if not ip:
print(f"[!] VM 정보 없음: {vm_name}")
else:
print(f"[+] {vm_name} ({ip}) 에 접속합니다...")
query_sample_tables(ip)
💻 실행 예시
python query_sample_table.py --environment dev --db_type first
위의 경우 environment 는 dev 를 db_type 은 first 로 쿼리 요청을 하면 host 는 dev-first 에 있는 mariadb 검색 요청을 합니다.
3) JbDesk 와 통합
통합된 코드는 다음에서 다운받을 수 있습니다.
Env 에서 environment 는 dev 를 db_type 은 first 로 쿼리 요청 시 dev-first 의 MariaDb 에서 검색 요청이 이루어집니다.
git clone git@github.com:jbpark/jbDesk.git
cd jbDesk
'유틸리티 > JbDesk' 카테고리의 다른 글
JbDesk 3.2편-fabric 으로 로그 메시지 추적 (4) | 2025.04.08 |
---|---|
JbDesk 3.1편-Python fabric 으로 원격 커맨드 실행 (0) | 2025.04.08 |
JbDesk 2.2편-Database 검색 (Oracle + SQLAlchemy) (0) | 2025.04.03 |
JbDesk 2.1편-Database 검색 (SQLite + SQLAlchemy) (0) | 2025.03.28 |
JbDesk 1.6편-코드 리팩터링 (1) | 2025.03.28 |