애플리케이션의 성능을 높이고 서버 자원을 효율적으로 관리하기 위해서는 레이트 리미팅(Rate Limiting) 과 캐싱(Caching) 전략을 적절히 사용하는 것이 중요합니다. 특히 대규모 사용자 기반을 가진 애플리케이션에서는 서버 과부하를 방지하고 빠른 응답 속도를 유지하는 데 필수적입니다. 아래에서는 각 전략의 원리와 함께 구체적인 예제 코드를 살펴보겠습니다.
레이트 리미팅(Rate Limiting)
레이트 리미팅은 API 호출이나 데이터베이스 쿼리의 빈도를 제한하여 성능을 최적화하고, 자원을 절약하며, 서비스 오버로드를 방지하는 데 유용합니다. 예를 들어, 특정 IP 주소에서 1시간 동안 100번 이상 요청할 경우 접근을 차단하는 식으로 빈도 제한을 설정할 수 있습니다.
구현 전략: IP 주소 또는 사용자별로 특정 시간 안에 호출 가능한 요청 수를 설정하여 서버의 안정성을 유지합니다. Redis와 같은 메모리 기반 데이터 스토어를 사용해 호출 빈도를 기록하고, 초과 요청 시 대기나 실패 응답을 반환할 수 있습니다.
예제: Node.js + Redis를 이용한 레이트 리미팅
아래 코드는 Node.js와 Redis를 사용하여 특정 IP 주소에서 일정 시간 안에 호출 가능한 요청 수를 제한하는 방법을 보여줍니다.
const redis = require('redis');
const client = redis.createClient();
function rateLimiter(req, res, next) {
const userIP = req.ip;
client.incr(userIP, (err, count) => {
if (count > 100) { // 예: 1시간 동안 100번 이상 요청 금지
res.status(429).send("요청 제한 초과");
} else {
client.expire(userIP, 3600); // 1시간 동안 카운트 유지
next();
}
});
}
위 코드에서 rateLimiter 함수는 클라이언트의 IP를 기반으로 요청 횟수를 Redis에 기록하며, 설정한 제한 횟수를 초과하면 429 상태 코드로 요청을 차단합니다. 이를 통해 서버의 과부하를 방지할 수 있습니다.
캐싱(Caching)
캐싱은 반복적으로 요청되는 데이터를 저장해 두고 빠르게 반환하는 방법으로, API나 데이터베이스의 호출 빈도를 줄여 성능을 크게 향상시킬 수 있습니다. 특히 오래 걸리는 데이터 처리 작업이나 고빈도 요청에서 효과적입니다.
캐싱 레이어 설정: 클라이언트, 서버, 데이터베이스 수준에서 캐싱을 적용할 수 있으며, Redis 같은 인메모리 스토리지를 통해 빈번히 요청되는 데이터를 캐시할 수 있습니다. 또한 TTL(Time-to-Live)을 설정하여 오래된 데이터를 자동으로 갱신하거나 삭제해 실시간성을 유지할 수 있습니다.
예제: Python + Flask + Redis를 이용한 캐싱
아래 코드는 Python의 Flask와 Redis를 사용하여 데이터베이스에서 가져온 데이터를 캐싱하여 빠르게 반환하는 예제입니다.
from flask import Flask, request
import redis
app = Flask(__name__)
cache = redis.StrictRedis(host='localhost', port=6379, db=0)
@app.route('/data')
def get_data():
data_key = 'expensive_data'
cached_data = cache.get(data_key)
if cached_data:
return cached_data
else:
data = retrieve_expensive_data() # 오래 걸리는 데이터 처리 함수
cache.setex(data_key, 3600, data) # 1시간 동안 캐싱
return data
위 코드에서 get_data 함수는 먼저 Redis에 저장된 expensive_data 캐시를 확인하고, 캐시된 데이터가 있으면 즉시 반환합니다. 만약 없다면 retrieve_expensive_data 함수를 호출하여 데이터를 생성한 후, Redis에 1시간 동안 캐싱합니다. 이렇게 함으로써 동일한 요청에 대한 응답 시간을 대폭 줄일 수 있습니다.
결론
레이트 리미팅과 캐싱은 고급 애플리케이션에서 성능을 최적화하는 데 핵심적인 전략입니다. 레이트 리미팅은 서버의 안정성을 유지하며, 캐싱은 응답 속도를 향상시키는 데 기여합니다. 이 두 가지 전략을 결합하여 API 서버에서 캐시된 응답을 사용하되 요청이 많아질 경우 레이트 리미팅을 적용하면 서버 성능을 최적화할 수 있습니다.
Mix-in은 Ruby 언어에서 매우 중요한 개념으로, 다중 상속의 복잡성을 피하면서도 여러 클래스에 기능을 공유할 수 있게 해줍니다. 이 글에서는 Ruby의 Mix-in 기능과 함께, 이를 다른 언어에서 어떻게 유사하게 구현할 수 있는지 소개하겠습니다.
1. Ruby의 Mix-in 예제
Ruby에서 Mix-in은 모듈을 통해 구현되며, 클래스에 포함(include)하여 여러 메서드를 쉽게 추가할 수 있습니다. 예제는 다음과 같습니다:
module Walkable
def walk
"I'm walking!"
end
end
class Animal
end
class Dog < Animal
include Walkable
end
dog = Dog.new
puts dog.walk # 출력: "I'm walking!"
여기서 Walkable 모듈을 Dog 클래스에 포함함으로써 Dog 클래스는 walk 메서드를 사용할 수 있습니다. Ruby는 이처럼 모듈을 활용해 코드 재사용성을 높입니다.
2. Python에서의 Mix-in 유사 기능
Python은 다중 상속을 직접 지원하므로, Mix-in 클래스를 상속하는 방식으로 유사한 패턴을 구현할 수 있습니다. Python에서의 예제는 다음과 같습니다:
class Walkable:
def walk(self):
return "I'm walking!"
class Animal:
pass
class Dog(Animal, Walkable):
pass
dog = Dog()
print(dog.walk()) # 출력: "I'm walking!"
이 예제에서는 Walkable 클래스를 Dog 클래스에 상속하여 walk 메서드를 추가했습니다. Python에서는 이런 방식으로 Mix-in을 구현합니다.
3. JavaScript에서의 Mix-in 구현
JavaScript는 클래스 기반 언어가 아니지만, 객체 지향 프로그래밍과 관련된 여러 패턴을 지원합니다. Mix-in은 Object.assign을 통해 구현할 수 있습니다:
let Walkable = {
walk: function() {
return "I'm walking!";
}
};
class Animal {}
class Dog extends Animal {}
Object.assign(Dog.prototype, Walkable);
let dog = new Dog();
console.log(dog.walk()); // 출력: "I'm walking!"
여기서 Object.assign을 사용해 Walkable 객체의 메서드를 Dog 클래스의 프로토타입에 복사하여 Mix-in 패턴을 적용했습니다.
4. C#에서의 Mix-in 구현
C#은 다중 상속을 지원하지 않지만, 인터페이스와 조합하여 Mix-in과 비슷한 구조를 만들 수 있습니다. 예제는 다음과 같습니다:
using System;
interface IWalkable
{
void Walk();
}
class Walkable : IWalkable
{
public void Walk()
{
Console.WriteLine("I'm walking!");
}
}
class Animal
{
}
class Dog : Animal, IWalkable
{
private Walkable _walkable = new Walkable();
public void Walk()
{
_walkable.Walk();
}
}
class Program
{
static void Main()
{
Dog dog = new Dog();
dog.Walk(); // 출력: "I'm walking!"
}
}
여기서는 Walkable 클래스를 별도로 만들어 Dog 클래스에서 해당 인스턴스를 사용해 메서드를 호출하는 방식으로 구현했습니다.
5. Java에서의 Mix-in 구현
Java는 기본적으로 다중 상속을 지원하지 않지만, Java 8부터는 default 메서드를 포함한 인터페이스를 사용하여 Mix-in을 구현할 수 있습니다:
interface Walkable {
default String walk() {
return "I'm walking!";
}
}
class Animal {
}
class Dog extends Animal implements Walkable {
}
public class Main {
public static void main(String[] args) {
Dog dog = new Dog();
System.out.println(dog.walk()); // 출력: "I'm walking!"
}
}
여기서는 Walkable 인터페이스에 default 메서드를 정의하고, Dog 클래스가 이를 구현하도록 했습니다.
결론
Ruby의 Mix-in은 모듈을 사용해 클래스에 쉽게 기능을 추가할 수 있게 해주며, Python, JavaScript, C#, Java와 같은 언어에서도 유사한 기능을 구현할 수 있습니다. 각 언어는 자신만의 방식을 사용하지만, 핵심 개념은 다중 상속의 복잡성을 줄이고 코드 재사용성을 높이는 데 있습니다.
이렇게 다양한 언어에서 Mix-in 패턴을 활용하면 더 효율적이고 유지보수가 쉬운 코드를 작성할 수 있습니다.
CCTZ에는 <chrono>와 협력하여 C++ 프로그래머에게 간단하고 올바른 방식으로 날짜, 시간대, 시간대를 가진 컴퓨팅에 필요한 모든 도구를 제공하는 두 개의 라이브러리가 포함되어 있습니다.
CCTZ의 라이브러리는 다음과 같습니다:
지역시간(Civil-Time) 라이브러리 - 날짜(cctz::civil_day 클래스로 표시됨)와 같은 지역 규모의 시간으로 컴퓨팅을 지원하는 헤더 전용 라이브러리입니다. 이 라이브러리는 include/cctz/civil_time.h 에 정의되어 있습니다.
시간대(Time-Zone) 라이브러리 - 이 라이브러리는 절대 시간과 지역 시간을 변환하기 위해 시스템에 설치된 IANA 시간대 데이터베이스를 사용합니다. 이 라이브러리는 include/cctz/time_zone.h 에 정의되어 있습니다.
이 라이브러리는 현재 Linux, Mac OS X 및 Android에서 작동하는 것으로 알려져 있습니다.
zoneinf 파일을 설치하면 Windows에서도 작동합니다. 하지만 대신 Windows 시간 API를 호출하는 cctz:TimeZoneIf 인터페이스를 구현하는 데 관심이 있습니다. 기여에 관심이 있으시면 연락해 주세요.
시작하기
CCTZ는 Bazel 빌드 시스템과 Google 테스트 프레임워크를 사용하여 구축 및 테스트하는 것이 가장 좋습니다. (Bazel을 사용할 수 없는 경우 작동하는 간단한 Makefile과 CMakeLists.txt도 있습니다.)
[여기에 제시된 개념은 문제 영역에 대한 일반적인 진실을 설명하며 라이브러리와 언어에 구애받지 않습니다. 이러한 개념을 이해하면 프로그래머가 가장 복잡한 시간 프로그래밍 문제도 올바르게 추론하고 가능한 가장 간단한 솔루션을 생성하는 데 도움이 됩니다.]
컴퓨터 프로그램에서 시간에 대해 생각하는 방법에는 절대 시간과 지역 시간이라는 두 가지 주요 방법이 있습니다. 두 가지 모두 사용법이 있으며, 각 시간대가 언제 적절한지 이해하는 것이 중요합니다. 절대 시간과 지역 시간은 시간대를 사용하여 앞뒤로 변환할 수 있으며, 이것이 두 시간대를 올바르게 변환할 수 있는 유일한 방법입니다. 이제 시간 프로그래밍의 세 가지 주요 개념인 절대 시간, 지역 시간, 시간대에 대해 자세히 살펴보겠습니다.
절대 시간은 고유하고 보편적으로 특정 순간을 나타냅니다. 캘린더, 날짜, 하루 중 시간에 대한 개념은 없습니다. 대신 실시간의 흐름을 측정하는 척도이며, 일반적으로 어떤 시대 이후의 단순한 틱 수로 사용됩니다. 절대 시간은 모든 시간대와 독립적이며 일광 절약 시간(DST)과 같은 인간이 부과하는 복잡성으로 인해 발생하지 않습니다. 많은 C++ 유형이 절대 시간을 나타내기 위해 존재하며, 고전적으로는 time_t, 그리고 최근에는 std:chrono:time_point.
지역 시간은 일반적인 일에 대한 법적으로 인정되는 시간을 표현하는 것입니다(https://www.merriam-webster.com/dictionary/civil) 참조). 지역 시간은 연도, 월, 일, 시간, 분, 초의 6가지 분야(때로는 "YMDHMS"로 단축되기도 함)로 구성된 인간 규모의 시간 표현이며, 24시간을 60분 시간과 60초로 나눈 프롤레틱 그레고리안 캘린더의 규칙을 따릅니다. 절대 시간과 마찬가지로 지역 시간도 모든 시간대와 관련 복잡성(예: DST)과 독립적입니다. std:tm에는 6개의 지역 시간 필드(YMDHMS)가 포함되어 있지만, 지역 시간의 규칙을 시행하는 행동이 없습니다.
시간대는 절대 시간 영역과 지역 시간 영역 간에 변환하기 위해 인간이 정의한 규칙을 공유하는 지정학적 영역입니다. 시간대의 규칙에는 UTC 시간 표준에서 해당 지역의 상쇄, 일광 절약 조정, 짧은 약어 문자열과 같은 것이 포함됩니다. 시간대에는 특정 지역의 지방 정부가 마음대로 규칙이 변경될 수 있기 때문에 특정 기간에만 적용되는 이질적인 규칙의 역사가 있는 경우가 많습니다. 이러한 이유로 시간대 규칙은 일반적으로 런타임에 절대 시간대와 지역 시간대 간의 변환을 수행하는 데 사용되는 데이터 스냅샷으로 컴파일됩니다. 현재 임의의 시간대를 지원하는 C++ 표준 라이브러리는 없습니다.
프로그래머가 이러한 개념을 올바르게 다루는 애플리케이션을 추론하고 프로그래밍하려면 위의 개념을 올바르게 구현하는 라이브러리가 있어야 합니다. CCTZ는 위의 개념을 완전히 구현하기 위해 기존 C++11 라이브러리에 추가됩니다.
절대 시간(Absolute time) - 이는 수정 없이 기존 C++11 <chrono> 라이브러리에서 구현됩니다. 예를 들어, 절대 시점은 std::chrono::time_point로 표시됩니다.
지역시간(Civil Time) - 이 기능은 CCTZ의 일부로 제공되는 include/cctz/civil_time.h 라이브러리에서 구현됩니다. 예를 들어, "날짜"는 cctz::civil_day로 표시됩니다.
시간대(Time zone) - 이 기능은 CCTZ의 일부로 제공되는 include/cctz/time_zone.h 라이브러리에 의해 구현됩니다. 예를 들어, 시간대는 클래스 cctz::time_zone의 인스턴스로 표시됩니다.
예시
안녕하세요 2016년 2월
이 "안녕 세상(Hello World)" 예제에서는 for-loop를 사용하여 2월 1일부터 3월까지 날짜를 반복합니다. 각 날짜는 출력으로 스트리밍되며, 날짜가 29일인 경우 요일도 출력합니다.
#include <iostream>
#include "cctz/civil_time.h"
int main() {
for (cctz::civil_day d(2016, 2, 1); d < cctz::civil_month(2016, 3); ++d) {
std::cout << "Hello " << d;
if (d.day() == 29) {
std::cout << " <- leap day is a " << cctz::get_weekday(d);
}
std::cout << "\n";
}
}
위 프로그램의 출력은 다음과 같습니다.
Hello 2016-02-01
Hello 2016-02-02
Hello 2016-02-03
[...]
Hello 2016-02-27
Hello 2016-02-28
Hello 2016-02-29 <- leap day is a Monday
하나의 거대한 도약
이 예제는 세 라이브러리(, civil time, time zone)를 모두 함께 사용하는 방법을 보여줍니다. 이 예제에서는 뉴욕의 시청자들이 1969년 7월 20일 오후 10시 56분에 닐 암스트롱의 첫 달맞이 산책을 시청한 것으로 알고 있습니다. 하지만 호주 시드니에서 친구가 몇 시에 시청했는지 알고 싶습니다.
#include <iostream>
#include "cctz/civil_time.h"
#include "cctz/time_zone.h"
int main() {
cctz::time_zone nyc;
cctz::load_time_zone("America/New_York", &nyc);
// Converts the input civil time in NYC to an absolute time.
const auto moon_walk =
cctz::convert(cctz::civil_second(1969, 7, 20, 22, 56, 0), nyc);
std::cout << "Moon walk in NYC: "
<< cctz::format("%Y-%m-%d %H:%M:%S %Ez\n", moon_walk, nyc);
cctz::time_zone syd;
if (!cctz::load_time_zone("Australia/Sydney", &syd)) return -1;
std::cout << "Moon walk in SYD: "
<< cctz::format("%Y-%m-%d %H:%M:%S %Ez\n", moon_walk, syd);
}
위 프로그램의 출력은 다음과 같습니다.
Moon walk in NYC: 1969-07-20 22:56:00 -04:00
Moon walk in SYD: 1969-07-21 12:56:00 +10:00
이 예는 달에서 첫 걸음의 절대 시간(std::chrono::time_point)이 시청자의 시간대에 관계없이 동일하다는 것을 보여줍니다(format ()에 대한 두 통화 모두에서 동일한 시간대가 사용됨). 유일한 차이점은 moon_walk 시점이 렌더링되는 시간대입니다. 이 경우 시드니에 있는 친구가 아마도 그 역사적인 사건을 보면서 점심을 먹고 있었을 것이라는 것을 알 수 있습니다.
이 명령은 라이브러리 및 헤더 파일을 플랫폼의 기본 위치(일반적으로 Linux 및 MacOS에서는 /usr/local/, Windows에서는 C:\Program Files)에 설치합니다.
--prefix 인수를 사용하여 다른 위치를 설정할 수 있습니다.
플랫폼에 따라 디버그 라이브러리와 릴리스 라이브러리를 모두 설치하지 못할 수도 있습니다.
Linux와 MacOS에서는 릴리스 라이브러리를 디버그 실행 파일과 릴리스 실행 파일 모두에 사용할 수 있기 때문에 이 문제는 큰 문제가 아닙니다.
하지만 라이브러리의 구성이 라이브러리에 연결되는 실행 파일의 구성과 같아야 하는 Windows의 경우에는 그렇지 않습니다.
따라서 Windows에서는 OpenXLSX 소스 폴더를 CMake 프로젝트의 하위 디렉토리로 포함하는 것이 훨씬 쉬워서 많은 골칫거리를 줄일 수 있습니다.
현재 상태
OpenXLSX는 아직 작업 중입니다.
다음은 구현되었으며 제대로 작동해야 하는 기능 목록입니다:
파일 생성/열기/저장
셀 내용 읽기/쓰기/수정
셀 및 셀 범위 복사
워크시트 복사
셀 범위 및 반복자
행 범위 및 반복자
포맷, 플롯 및 도형과 관련된 기능은 구현되지 않았으며 가까운 미래에 구현될 계획도 없습니다.
현재 XLD 문서 객체를 생성하는 것이 작동하지 않는다는 점에 유의해야 합니다!
성능
아래 표는 초당 약 4,000,000셀의 속도로 읽기/쓰기 액세스를 수행할 수 있음을 보여주는 벤치마크(Google Benchmark 라이브러리 사용)의 출력입니다.
.xml 파일의 문자열로 변환하거나 문자열에서 변환하기 때문에 부동소수점 숫자는 다소 낮습니다.
Run on (16 X 2300 MHz CPU s)
CPU Caches:
L1 Data 32 KiB (x8)
L1 Instruction 32 KiB (x8)
L2 Unified 256 KiB (x8)
L3 Unified 16384 KiB (x1)
Load Average: 2.46, 2.25, 2.19
---------------------------------------------------------------------------
Benchmark Time CPU Iterations UserCounters...
---------------------------------------------------------------------------
BM_WriteStrings 2484 ms 2482 ms 1 items=8.38861M items_per_second=3.37956M/s
BM_WriteIntegers 1949 ms 1949 ms 1 items=8.38861M items_per_second=4.30485M/s
BM_WriteFloats 4720 ms 4719 ms 1 items=8.38861M items_per_second=1.77767M/s
BM_WriteBools 2167 ms 2166 ms 1 items=8.38861M items_per_second=3.87247M/s
BM_ReadStrings 1883 ms 1882 ms 1 items=8.38861M items_per_second=4.45776M/s
BM_ReadIntegers 1641 ms 1641 ms 1 items=8.38861M items_per_second=5.11252M/s
BM_ReadFloats 4173 ms 4172 ms 1 items=8.38861M items_per_second=2.01078M/s
BM_ReadBools 1898 ms 1898 ms 1 items=8.38861M items_per_second=4.4205M/s
주의사항
파일 크기
.xlsx 파일은 기본적으로 .zip 아카이브의 .xml 파일 묶음입니다.
내부적으로 OpenXLSX는 miniz 라이브러리를 사용하여 .zip 아카이브를 압축/압축 해제하는데, miniz는 처리할 수 있는 파일 크기에 대한 상한이 있는 것으로 밝혀졌습니다.
아카이브의 파일(아카이브 자체가 아닌 .zip 아카이브의 항목)에 대한 최대 허용 파일 크기는 4GB(압축되지 않음)입니다.
일반적으로 .xlsx 파일/아카이브에서 가장 큰 파일은 워크시트 데이터가 저장된 .xml 파일입니다.
즉, 워크시트 데이터는 4GB를 초과할 수 없습니다.
행과 열로 환산하면 데이터 유형에 따라 크게 달라지지만, 4자리 정수로 채워진 1,048,576개의 행 x 128개의 열이 차지합니다.
압축된 아카이브의 크기는 워크시트에 저장된 데이터와 사용된 압축 알고리즘에 따라 다르지만, 워크시트가 4GB인 워크북의 압축 크기는 일반적으로 300~350 MB입니다.
4GB 제한은 아카이브의 전체 아카이브 크기가 아닌 단일 항목에만 관련이 있습니다.
즉, 아카이브에 4GB 크기의 항목이 여러 개 있는 경우 miniz는 여전히 이를 처리할 수 있습니다. OpenXLSX의 경우 여러 개의 대형 워크시트가 있는 워크북을 계속 열 수 있습니다.
메모리 사용량
OpenXLSX는 .xlsx 아카이브에서 .xml 파일을 구문 분석하고 조작하기 위해 PugiXML 라이브러리를 사용합니다.
PugiXML은 전체 .xml 문서를 메모리로 읽는 DOM 파서입니다.
따라서 구문 분석과 조작이 엄청나게 빠릅니다.
그러나 모든 선택에는 결과가 있으며, DOM 파서를 사용하면 많은 메모리가 필요할 수도 있습니다.
작은 스프레드시트의 경우 문제가 되지 않지만 큰 스프레드시트를 조작해야 하는 경우 많은 메모리가 필요할 수 있습니다.
아래 표는 OpenXLSX가 처리할 수 있는 데이터 열 수를 나타냅니다(1,048,576행으로 가정):
Columns
8GB RAM
8-16
16GB RAM
32-64
32GB RAM
128-256
마일리지는 다를 수 있습니다.
OpenXLSX의 성능은 스프레드시트의 데이터 유형에 따라 달라집니다.
또한 64비트 모드에서는 OpenXLSX를 사용하는 것이 좋습니다.
32비트 모드에서는 쉽게 사용할 수 있지만 4GB RAM에만 액세스할 수 있으므로 대규모 스프레드시트를 처리할 때 유용성이 크게 제한됩니다.
메모리 소비가 문제인 경우 컴팩트 모드에서 OpenXLSX 라이브러리를 구축하면(CMakeLists.txt 파일에서 ENABLE_COMPACT_MODE를 찾아보세요) PugiXML의 컴팩트 모드를 활성화할 수 있습니다.
UTF-8은 Linux와 MacOS에서 잘 지원되지만 Windows에서는 지원이 더 제한적입니다.
예를 들어, 터미널 창에 ASCII가 아닌 문자(예: 중국어 또는 일본어 문자)가 출력되면 횡설수설하는 것처럼 보일 수 있습니다.
앞서 언급했듯이 소스 파일 자체의 텍스트 인코딩도 염두에 두어야 할 때도 있습니다.
일부 사용자는 ASCII가 아닌 파일 이름으로 .xlsx 파일을 열거나 만들 때 OpenXLSX가 충돌하는 문제가 있었는데, 테스트 프로그램의 소스 코드가 UTF-8 인코딩이 아닌 것으로 밝혀졌기 때문에 OpenXLSX에 입력되는 문자열도 UTF-8이 아닌 것으로 나타났습니다.
정상 상태를 유지하려면 소스 코드 파일은 항상 UTF-8 파일에 있는 것이 좋으며, 제가 아는 모든 IDE는 UTF-8 인코딩의 소스 코드 파일을 처리할 수 있습니다. Windows🤮의 멋진 유니코드 세계에 오신 것을 환영합니다
Zip 라이브러리
엑셀 파일은 기본적으로 .zip 아카이브로 포장된 .xml 파일 묶음에 불과합니다.
OpenXLSX는 타사 라이브러리를 사용하여 .zip 아카이브에서 .xml 파일을 추출합니다.
OpenXLSX에서 사용하는 기본 라이브러리는 미니즈를 중심으로 한 객체 지향 래퍼인 Zippy입니다.
미니즈 라이브러리는 빠르고 헤더 전용으로, OpenXLSX에 이상적입니다.
하지만 원한다면 다른 지퍼 라이브러리를 사용할 수도 있습니다.
드물게 미니즈의 안정성 문제가 발생할 수도 있습니다.
이러한 경우 다른 지퍼 라이브러리를 사용해 보는 것이 유용할 수 있습니다.
Zippy/miniz 라이브러리를 사용하려면 특별한 노력이 필요하지 않으며, 즉시 사용할 수 있습니다.
하지만 다른 Zip 라이브러리를 사용하려면 몇 가지 작업이 필요합니다.
다른 Zip 라이브러리를 사용하려면 IZipArchive 클래스에서 지정한 인터페이스를 준수하는 래퍼 클래스를 만들어야 합니다.
이 클래스는 형식 삭제를 사용하여 구현되므로 상속이 필요하지 않으며 클래스에 적합한 인터페이스만 있으면 됩니다.
그런 다음 클래스의 개체를 제공하고 OpenXLSX 생성자에게 제공합니다.
이 작업의 예를 보려면 예제 폴더에서 데모1A를 살펴보세요. 이 예제에서는 예제/외부/커스텀Zip 아래에서 찾을 수 있는 커스텀Zip(libzip을 zip 라이브러리로 사용)이라는 클래스를 사용합니다.
예제 프로그램을 구축하려면 컴퓨터에 libzip(및 종속성)이 설치되어 있는지 확인하고 OpenXLSX 루트의 CMakeLists.txt 파일에서 OPENXLSX_ENABLE_LIBZIP 옵션을 활성화합니다.
앞서 언급했듯이 데모1A 예제 프로그램은 libzip을 사용합니다.
libzip은 매우 안정적인 라이브러리이며 널리 사용되고 있습니다.
하지만 제 경험에 따르면 대형 스프레드시트와 같은 대형 zip 파일의 경우 상당히 느립니다.
이러한 이유로 libzip이 이상적인 솔루션은 아니지만 다른 zip 라이브러리를 어떻게 사용할 수 있는지 보여주는 데 유용합니다.
예제 프로그램
'Examples' 폴더에는 OpenXLSX 사용 방법을 설명하는 몇 가지 예제 프로그램이 있습니다.
이러한 예제 프로그램을 공부하는 것이 OpenXLSX 사용 방법을 배우는 가장 좋은 방법입니다.
Tuple은 다양한 프로그래밍 언어에서 데이터 집합을 효율적으로 표현하는 자료 구조로 사용됩니다. 여러 개의 값을 하나의 묶음으로 표현할 수 있다는 점에서 배열과 유사하지만, 다루는 방식이나 특성은 언어에 따라 차이를 보입니다. 특히, 불변성(Immutable)의 여부는 언어마다 다르게 구현될 수 있습니다. 이번 글에서는 여러 언어에서의 Tuple 개념과 불변성에 대해 알아보겠습니다.
Python의 Tuple
Python의 tuple은 대표적인 불변 자료 구조입니다. 생성된 후에는 요소를 수정, 추가, 삭제할 수 없습니다. 이러한 불변성은 코드에서 의도하지 않은 데이터 변조를 방지하고, 메모리 사용 효율을 높이는 데 기여합니다. tuple은 소괄호 ()를 사용해 정의되며, 여러 자료형을 함께 저장할 수 있습니다.
예제:
my_tuple = (10, 20, 30)
# my_tuple[1] = 40 # 오류 발생: 요소를 변경할 수 없음
C++의 Tuple
C++에서는 std::tuple을 사용해 다양한 자료형을 하나로 묶을 수 있습니다. 그러나 std::tuple 자체는 불변성을 강제하지 않으며, 필요에 따라 const 키워드를 사용해 요소의 변경을 막을 수 있습니다. 따라서 C++에서 tuple을 불변으로 사용할지 여부는 프로그래머의 선택에 달려 있습니다.
Swift의 튜플은 직관적이며 여러 값을 쉽게 묶을 수 있도록 합니다. 하지만 기본적으로 불변성을 갖지 않으며, let 키워드를 사용해 불변성을 지정할 수 있습니다. 이렇게 하면 튜플의 값을 수정할 수 없게 됩니다.
예제:
let myTuple = (404, "Not Found")
// myTuple은 불변이므로 수정할 수 없음
JavaScript와 TypeScript의 Tuple
JavaScript는 tuple 개념을 제공하지 않지만, TypeScript에서는 정해진 순서와 자료형을 갖는 tuple을 정의할 수 있습니다. 불변성을 보장하기 위해 const 키워드를 사용할 수 있지만, 여전히 요소의 값을 변경할 수 있는 점은 주의가 필요합니다.
예제 (TypeScript):
const myTuple: [number, string] = [1, "Hello"];
// 요소 값은 여전히 변경 가능
Haskell의 Tuple
Haskell에서는 모든 값이 기본적으로 불변성을 갖습니다. 따라서 Haskell의 tuple 역시 불변이며, 데이터의 무결성을 보장합니다. 이러한 특성은 함수형 프로그래밍의 철학과 일치합니다.
예제:
let myTuple = (1, "Hello", 3.14)
-- myTuple의 요소는 수정할 수 없음
결론
Tuple은 언어마다 특성과 활용 방법이 다릅니다. Python과 Haskell에서는 tuple이 불변성을 갖지만, C++, Swift, TypeScript에서는 필요에 따라 불변성을 구현할 수 있습니다. 이처럼 tuple의 불변성 여부는 언어 설계 철학과 목적에 따라 결정되며, 효율적인 데이터 관리를 위해 각 언어의 특징을 이해하는 것이 중요합니다.
SOCI는 표준 C++ 범위 내에서 일반 C++ 코드에 SQL 쿼리를 내장한 것 같은 효과를 내는 C++용 데이터베이스 액세스 라이브러리입니다.
이 아이디어는 C++ 프로그래머에게 가장 자연스럽고 직관적인 방식으로 SQL 데이터베이스에 액세스할 수 있는 방법을 제공하는 것입니다. 기존 라이브러리가 필요에 비해 너무 어렵거나 방해가 된다면 SOCI가 좋은 대안이 될 수 있습니다.
단일 행을 검색해야 하는 SQL 쿼리에 대한 가장 간단한 동기 부여 코드 예는 다음과 같습니다.
int id = ...;
string name;
int salary;
sql << "select name, salary from persons where id = " << id,
into(name), into(salary);
그리고 객체 관계 매핑에 대한 광범위한 지원으로 다음과 같은 이점을 얻을 수 있습니다.
int id = ...;
Person p;
sql << "select first_name, last_name, date_of_birth "
"from persons where id = " << id,
into(p);
STL과의 통합도 지원됩니다.
Rowset<string> rs = (sql.prepare << "select name from persons");
copy(rs.begin(), rs.end(), ostream_iterator<string>(cout, "\n"));
SOCI는 Boost 데이터 유형(선택 사항, 튜플 및 퓨전)과의 광범위한 통합과 사용자 정의 데이터 유형에 대한 유연한 지원을 제공합니다.
SOCI는 주로 C++ 라이브러리이지만, 다른 프로그래밍 언어에서도 사용할 수 있습니다. 현재 패키지에는 Ada 바인딩이 포함되어 있으며, 앞으로 더 많은 바인딩이 추가될 가능성이 있습니다.
2.0.0 릴리스부터 SOCI는 백엔드에 플러그인 아키텍처를 사용합니다. 이를 통해 다양한 데이터베이스 서버를 타겟팅할 수 있습니다. 현재(4.0.2) 다음 데이터베이스 시스템이 지원됩니다.
DB2
Firebird
MySQL
ODBC (generic backend)
Oracle
PostgreSQL
SQLite3
라이브러리의 의도는 가능한 한 많은 데이터베이스 기술을 포괄하는 것입니다. 이를 위해 프로젝트는 기존 데이터베이스 인터페이스에 대한 전문 지식을 가지고 있고 전담 백엔드 작성을 돕고 싶어하는 다른 프로그래머의 자원 봉사 기여에 의존해야 합니다.
참여에 관심이 있으시면 프로젝트 관리자 에게 문의하세요.
Desktop Qt-Applications를 위한 글로벌 단축키shortcut/핫키hotkey 입니다.
QHotkey는 핫키/글로벌 단축키를 만드는 데 사용할 수 있는 클래스로, 애플리케이션 상태와 무관하게 어디에서나 작동하는 단축키입니다.
즉, 애플리케이션이 활성화, 비활성화, 최소화 또는 전혀 표시되지 않더라도 단축키를 받을 수 있습니다.
특징
Windows, Mac 및 X11에서 작동합니다.
사용이 간편하고 QKeySequence를 사용하여 간편한 단축키 입력이 가능합니다.
거의 모든 일반 키 지원(OS 및 키보드 레이아웃에 따라 다름)
키/수정자 조합을 직접 입력할 수 있습니다.
동일한 단축키에 대해 여러 QHotkey 인스턴스 지원(최적화 포함)
스레드 안전 - 모든 스레드에서 사용 가능(스레드 안전 섹션 참조)
필요한 경우 기본 키코드 및 수정자 사용 허용
참고: 현재 Wayland는 지원되지 않습니다. Wayland로 글로벌 단축키를 등록할 수 없기 때문입니다. 자세한 내용이나 Wayland에서 단축키를 작동시키는 방법에 대한 아이디어는 Issue #14 를 참조하세요.
빌딩
QHotkey는 Qt6와 Qt5를 모두 지원합니다. Qt6를 사용하는 경우 버전 6.2.0 이상이 필요합니다. CMake 빌드 시스템을 사용하여 빌드할 수 있습니다.
CMake
CMake QT_DEFAULT_MAJOR_VERSION 변수는 빌드에 사용되는 Qt의 주요 버전을 제어하며 기본값은 5입니다. 예를 들어, Qt6로 빌드하려면 CMake 명령줄 옵션 -DQT_DEFAULT_MAJOR_VERSION=6을 사용합니다. 테스트 애플리케이션 QHotkeyTest를 빌드하려면 -DQHOTKEY_EXAMPLES=ON을 지정합니다.
Windows 사용자에게 중요: QPM 버전 0.10.0(웹사이트에서 다운로드할 수 있는 버전)은 현재 Windows에서 작동하지 않습니다! 마스터에서 이미 수정되었지만 아직 출시되지 않았습니다. 새 버전이 출시될 때까지 여기에서 최신 개발 빌드를 다운로드할 수 있습니다.
이렇게 하면 QHotkey의 모든 경고가 꺼집니다(지금은 경고만 사용하므로 이것으로 충분합니다). 로깅 범주로 할 수 있는 모든 작업에 대한 자세한 내용은 Qt 문서를 확인하세요.
스레드 안전성
QHotkey 클래스 자체는 재진입 가능합니다. 즉, 모든 스레드에서 필요한 만큼 많은 인스턴스를 만들 수 있습니다. 이를 통해 모든 스레드에서 QHotkey를 사용할 수 있습니다. 하지만 인스턴스가 속한 스레드와 다른 스레드에서는 QHotkey 인스턴스를 사용해서는 안 됩니다! 내부적으로 시스템은 핫키 이벤트를 처리하고 이를 QHotkey 인스턴스에 분배하는 싱글톤 인스턴스를 사용합니다. 이 내부 클래스는 완전히 스레드 안전합니다.
그러나 이 싱글톤 인스턴스는 메인 스레드에서만 실행됩니다. (한 가지 이유는 일부 OS 함수가 스레드 안전하지 않기 때문입니다.) 스레드 핫키를 가능하게 하기 위해 중요한 함수(핫키 등록/등록 해제 및 키 변환)도 모두 메인 스레드에서 실행됩니다. 다른 스레드의 QHotkey 인스턴스는 Qt::BlockingQueuedConnection과 함께 QMetaObject::invokeMethod를 사용합니다.
여러분에게 이는 다음을 의미합니다. 메인 스레드가 아닌 다른 스레드의 QHotkey 인스턴스는 핫키를 등록/등록 해제/변환하는 데 약간 더 오래 걸릴 수 있습니다. 메인 스레드가 이를 대신 수행할 때까지 기다려야 하기 때문입니다. 중요: 그러나 이 기능에는 한 가지 추가 제한이 있습니다. 메인 스레드가 아닌 다른 스레드의 QHotkey 인스턴스는 메인 이벤트 루프가 끝나기 전에 등록 해제되거나 파괴되어야 합니다. 그렇지 않으면 핫키 파괴 시 애플리케이션이 중단됩니다. 이 제한은 메인 스레드의 인스턴스에는 적용되지 않습니다. 또한 루프가 시작되기 전에 단축키를 변경하거나 등록/등록 해제하는 경우에도 실제로 시작될 때까지 동일한 일이 발생합니다.
문서는 doxygen 을 사용하여 작성되었습니다. 여기에는 HTML 문서와 Qt-Help 파일이 포함되어 있으며, 이를 QtCreator(QtAssistant)에 포함시켜 F1-Help를 표시할 수 있습니다(자세한 내용은 외부 문서 추가를 참조하세요).
기술적
요구 사항
명시적 지원은 최신 Qt LTS에만 제공되지만 이전 버전에서도 작동할 수 있습니다.
최소한 QtGui-Module(QGuiApplication). 콘솔 기반 애플리케이션의 단축키는 지원되지 않습니다(운영 체제에서). 그러나 보이는 창 없이 GUI 애플리케이션을 만들 수 있습니다.
C++11
알려진 제한 사항
단일 키/수정자 조합만 가능합니다. QKeySequence를 사용하는 경우 시퀀스의 첫 번째 키+수정자만 사용됩니다.
Qt::Key는 일반 숫자와 숫자패드 숫자 사이에 차이를 두지 않습니다. 그러나 대부분의 키보드는 이것을 요구합니다. 따라서 네이티브 단축키를 사용하지 않는 한 숫자패드에 대한 단축키를 등록할 수 없습니다.
모든 키를 지원하지는 않지만, 일반적인 키는 대부분 지원합니다. 플랫폼 간에 차이가 있으며 키보드 레이아웃에 따라 달라집니다. 예를 들어 "Delete"는 Windows와 Mac에서는 작동하지만 X11에서는 작동하지 않습니다(적어도 제 테스트 머신에서는 그렇습니다). 가능한 한 OS 함수를 사용하려고 했지만, Qt::Key 값을 네이티브 키로 변환해야 하기 때문에 몇 가지 제한이 있습니다. 필요한 키를 사용할 수 있으므로 네이티브 단축키를 사용해 보세요.
등록된 키는 QHotkey에서 "취득"됩니다. 즉, 핫키가 애플리케이션에서 사용된 후에는 활성 애플리케이션으로 전송되지 않습니다. 이는 운영 체제에서 이런 방식으로 수행되며 변경할 수 없습니다.
X11에서 QHotkey: Failed to register hotkey. Error: BadAccess (attempt to access private resource denied) 오류가 발생하는 경우 X11에 개인용 핫키를 등록하려고 한다는 의미입니다. 이러한 키는 일반 API를 사용하여 등록할 수 없습니다.
데이터를 다루다 보면 CSV 파일과 SQLite 데이터베이스(DB) 파일 간의 변환이 필요한 경우가 많습니다. Python의 pandas와 sqlite3 모듈을 활용하면 CSV 파일을 SQLite DB로 변환하거나, 반대로 SQLite DB를 CSV 파일로 변환하는 작업을 간단히 수행할 수 있습니다. 이 글에서는 각 변환 과정의 방법과 코드를 단계별로 설명합니다.
1. CSV 파일을 SQLite DB 파일로 변환하기
필요한 모듈 설치
pandas는 데이터를 처리하고 변환하는 데 유용한 라이브러리입니다. 설치가 필요하다면 다음 명령어를 실행합니다.
pip install pandas
CSV 파일을 SQLite DB로 변환하는 코드
아래 코드는 CSV 파일을 SQLite DB 파일로 변환하는 코드입니다. CSV 파일을 읽어와 SQLite 데이터베이스에 저장할 수 있습니다.
import pandas as pd
import sqlite3
# CSV 파일 불러오기
df = pd.read_csv('your_file.csv')
# SQLite DB 연결 생성 및 테이블에 데이터 입력
conn = sqlite3.connect('your_database.db')
df.to_sql('your_table_name', conn, if_exists='replace', index=False)
# 연결 종료
conn.close()
코드 설명
pandas의 read_csv 함수로 CSV 파일을 읽습니다.
sqlite3의 connect 함수로 SQLite DB를 생성하거나 연결한 뒤, to_sql 함수를 사용해 DataFrame을 DB의 테이블로 변환해 저장합니다.
if_exists='replace'는 동일한 이름의 테이블이 있을 경우 덮어쓰기합니다.
작업이 끝나면 conn.close()로 연결을 종료합니다.
2. SQLite DB 파일을 CSV 파일로 변환하기 (역변환)
이번에는 SQLite DB 파일을 CSV 파일로 내보내는 방법을 알아보겠습니다.
필요한 모듈 설치
위와 마찬가지로 pandas가 필요합니다. 설치가 필요하면 다음 명령어를 사용하세요.
pip install pandas
SQLite DB를 CSV 파일로 변환하는 코드
아래 코드는 SQLite DB 파일에서 데이터를 읽어와 CSV 파일로 저장하는 예제입니다.
import pandas as pd
import sqlite3
# SQLite DB 연결 및 테이블에서 데이터 불러오기
conn = sqlite3.connect('your_database.db')
df = pd.read_sql_query("SELECT * FROM your_table_name", conn)
# 데이터프레임을 CSV 파일로 저장
df.to_csv('output_file.csv', index=False)
# 연결 종료
conn.close()
코드 설명
pandas의 read_sql_query로 지정한 SQLite DB 테이블 데이터를 DataFrame으로 불러옵니다.
to_csv 함수를 사용하여 DataFrame을 CSV 파일로 저장합니다.
index=False를 설정해 DataFrame의 인덱스 값이 CSV 파일에 포함되지 않도록 합니다.
마지막으로 conn.close()로 연결을 종료하여 작업을 마칩니다.
결론
위와 같이 Python의 pandas와 sqlite3 모듈을 활용하여 CSV 파일을 SQLite DB 파일로 변환하거나, 반대로 SQLite DB 파일을 CSV 파일로 변환하는 방법을 손쉽게 구현할 수 있습니다. 이를 통해 데이터 처리 과정에서 다양한 파일 포맷을 자유롭게 활용할 수 있습니다.
첫 번째 미들웨어로도 사용할 수 있습니다(이미 기록 등을 수행하는 첫 번째 미들웨어가 있는 경우)
app.use([](auto &ctx, auto next, auto prev)
{
next([&ctx, prev]
{
// this is last code to be called before sending response to client
if(ctx.response.status() == 404)
ctx.response.body("Custom Not Found");
prev();
});
});