PHP 면접 준비

PHP 면접 질문

시니어리티 수준별로 분류한 PHP 백엔드 면접 질문 15개입니다. 기본기, 실무 트레이드오프, 시니어 수준의 프로덕션 판단을 복습하는 데 활용하세요.

PHP AI 면접 시작하기신용카드가 필요하지 않습니다. 무료 세션 1회 제공.
영어 기술 면접 연습비원어민이 기술 면접 통과를 연습할 수 있는 모드입니다.

주니어 질문

1PHP(PHP: Hypertext Preprocessor)의 타입 시스템은 약타입과 강타입을 어떻게 구분하며, declare(strict_types=1)을 활성화하면 무엇이 달라지나요?

PHP는 스칼라 타입 선언에 대해 기본적으로 약타입/강제 변환(coercive) 방식을 사용합니다. 함수에 스칼라 매개변수나 반환 타입을 선언할 때, 호환 가능한 값(예: 숫자 형태의 문자열)이 전달되면 거부하는 대신 숫자로 자동 변환할 수 있습니다. `declare(strict_types=1)`을 선언하면 해당 파일에 대해 스칼라 타입 검사가 엄격(strict)해져 호환되지 않는 스칼라 값의 강제 변환이 일어나지 않고 `TypeError`가 발생합니다. 이 지시문은 파일 단위 스코프를 가집니다. 사용자 정의 함수 인자의 경우 호출자(caller) 파일의 설정이 엄격도를 제어하며, return 문의 경우 함수가 정의된 파일의 설정이 반환값 엄격도를 제어합니다. 이 설정이 PHP 전체를 전역적으로 엄격하게 만들거나 언어 내의 모든 타입 변환을 비활성화하는 것은 아닙니다.

<?php
// lib.php
function takesInt(int $x): int {
    return $x;
}

// weak.php, no strict_types
echo takesInt('123'); // coerces string to int

// strict.php
declare(strict_types=1);
echo takesInt('123'); // TypeError
AI 코치와 함께 이 질문에 답해 보세요

2PHP에서 ==와 ===의 차이점을 설명하고 숫자형 문자열, 배열, 객체 및 in_array() 사용 시 주의해야 할 함정을 함께 다뤄주세요.

`==`는 느슨한 비교(loose comparison)를 수행하므로 PHP가 비교 전에 타입 변환(type juggling)을 수행할 수 있습니다. 반면 `===`는 엄격한 비교(strict comparison)를 수행하므로 강제 형 변환 없이 값과 타입이 모두 동일해야 합니다. 숫자 형태의 문자열(numeric strings)은 느슨한 비교 시 숫자로 처리될 수 있어 흔히 발생하는 함정 중 하나입니다. 배열과 객체 역시 고유한 비교 규칙을 가집니다. 배열은 키와 값을 기준으로 비교되며, 객체의 경우 `==`는 동일한 클래스의 인스턴스이면서 프로퍼티가 일치하는지 비교하고, `===`는 정확히 동일한 인스턴스를 가리켜야만 참을 반환합니다. `in_array()`는 기본적으로 느슨한 비교를 사용하므로, 타입까지 엄격하게 일치해야 한다면 세 번째 인자로 `true`를 전달해야 합니다.

<?php
var_dump(123 == '123');      // loose: numeric string is converted
var_dump(123 === '123');     // strict: int !== string

$values = [0, 1, 2];
var_dump(in_array('0', $values));       // loose
var_dump(in_array('0', $values, true)); // strict
AI 코치와 함께 이 질문에 답해 보세요

3PHP 반환 타입으로서 mixed, void, never의 차이점은 무엇이며, 각각 어떤 보장을 제공하나요?

반환 타입으로서 `mixed`, `void`, `never`는 서로 다른 보장을 제공합니다. `mixed`는 함수가 `null`을 포함한 모든 값을 반환할 수 있음을 의미합니다. `void`는 함수가 의미 있는 값을 반환하도록 의도되지 않았음을 의미하며, `return;`을 사용할 수는 있지만 표현식(expression)을 반환할 수는 없습니다. `never`는 함수가 호출자에게 정상적으로 제어를 반환하지 않음을 의미합니다. 즉, 항상 예외를 던지거나(throw), 종료(exit)되거나, 제어 흐름을 완전히 중단해야 합니다. `never` 함수를 정상적으로 호출한 이후에는 실행 흐름이 계속 이어질 수 없으므로, `never`는 `void`보다 더 강력한 제약을 갖습니다.

<?php
function parse(string $raw): mixed {
    return json_decode($raw, true);
}

function logMessage(string $message): void {
    error_log($message);
    return; // allowed
}

function fail(string $message): never {
    throw new RuntimeException($message);
}

logMessage('starting');
fail('stop');
echo 'unreachable';
AI 코치와 함께 이 질문에 답해 보세요

4PHP에서 iterable이란 무엇이며, array, Traversable, Iterator, Generator와는 어떤 관계가 있나요?

PHP에서 `iterable`은 “`array` 또는 `Traversable`을 구현한 객체”를 의미하는 타입 선언입니다. `Traversable`은 `foreach`로 순회할 수 있는 객체를 나타내는 기본 마커 인터페이스이며, 사용자 정의 코드에서는 일반적으로 `Traversable`을 상속하는 `Iterator`나 `IteratorAggregate`를 구현합니다. `Generator`는 `yield`를 사용하는 제너레이터 함수가 반환하는 객체이며, `Iterator`를 구현하므로 `Traversable`이기도 하여 `iterable`이 필요한 모든 곳에서 사용할 수 있습니다.

function printItems(iterable $items): void {
    foreach ($items as $item) {
        echo $item, PHP_EOL;
    }
}

printItems(['a', 'b']);

function letters(): Generator {
    yield 'x';
    yield 'y';
}

printItems(letters());
AI 코치와 함께 이 질문에 답해 보세요

5PHP (PHP: Hypertext Preprocessor)에서 public, protected, private 가시성 규칙은 무엇이며, 상속 및 트레이트(trait)와 어떻게 상호작용하나요?

PHP에서 `public` 멤버는 어디서나 접근할 수 있고, `protected` 멤버는 해당 멤버를 선언한 클래스와 하위 클래스에서 접근할 수 있으며, `private` 멤버는 선언한 클래스 내부에서만 접근할 수 있습니다. 상속 관계에서 하위 클래스는 상속받은 `public` 및 `protected` 멤버를 사용할 수 있지만, 부모의 `private` 멤버는 상속된 API로 접근할 수 없습니다. 메서드를 오버라이딩할 때 자식 클래스는 일반적으로 가시성을 축소할 수 없습니다. 예를 들어 부모의 `public` 메서드는 반드시 `public`으로 유지되어야 합니다. 트레이트(trait)는 이를 사용하는 클래스에 합성(compose)되며, 트레이트 멤버 역시 PHP 가시성 규칙을 따릅니다. 트레이트 메서드의 가시성은 트레이트를 가져올 때 `as` 키워드를 사용하여 변경하거나 별칭을 지정할 수도 있습니다.

<?php
class Base {
    public function a() {}
    protected function b() {}
    private function c() {}
}

trait Logs {
    public function log(string $message): void {}
}

class Child extends Base {
    use Logs { log as protected writeLog; }

    public function test(): void {
        $this->a();              // OK
        $this->b();              // OK
        // $this->c();           // Not accessible: private to Base
        $this->writeLog('ok');   // OK inside class, protected alias
    }
}

$child = new Child();
$child->a();
// $child->b();           // Not accessible from outside
// $child->writeLog('x'); // Not accessible from outside
AI 코치와 함께 이 질문에 답해 보세요

6PHP(PHP: Hypertext Preprocessor)에서 트레이트(trait)는 어떻게 동작하며, 여러 트레이트가 동일한 메서드를 정의할 때 충돌을 어떻게 해결하나요?

트레이트(trait)는 수평적 코드 재사용을 위한 PHP의 메커니즘입니다. 클래스는 하나 이상의 트레이트를 사용할 수 있으며, 트레이트의 멤버는 해당 클래스에 통합됩니다. 트레이트는 독립된 클래스가 아니므로 직접 인스턴스화할 수 없습니다. 여러 트레이트가 동일한 이름의 메서드를 제공하는 경우, PHP에서는 명시적인 충돌 해결이 필요합니다. `insteadof` 연산자는 충돌이 발생한 메서드명에 대해 어떤 트레이트 메서드를 사용할지 선택합니다. `as` 연산자는 트레이트 메서드에 대한 별칭(alias)을 생성할 수 있으며, 임포트된 메서드의 가시성(visibility)을 변경할 수도 있습니다. 클래스 자체에 정의된 메서드는 트레이트 메서드를 오버라이드하며, 트레이트 메서드는 상속받은 부모 클래스의 메서드보다 우선순위를 갖습니다.

<?php
trait JsonLogger {
    public function log(string $message): void {
        echo "json:$message\n";
    }
}

trait FileLogger {
    public function log(string $message): void {
        echo "file:$message\n";
    }
}

class Service {
    use JsonLogger, FileLogger {
        JsonLogger::log insteadof FileLogger;
        FileLogger::log as logToFile;
        JsonLogger::log as protected logJson;
    }
}

$service = new Service();
$service->log('hello');
$service->logToFile('hello');
// $service->logJson('hello'); // not accessible from outside; protected alias
AI 코치와 함께 이 질문에 답해 보세요

7순수 열거형(Pure Enum)과 백트 열거형(Backed Enum)을 포함한 PHP의 열거형(enum)이란 무엇이며, 도메인 모델링을 어떻게 개선합니까?

PHP 열거형(enum)은 주문 상태나 사용자 역할처럼 특정 타입에 대해 닫힌 명명된 케이스 집합을 정의합니다. 순수 열거형(Pure Enum)은 이름이 지정된 케이스만 가지며, 백트 열거형(Backed Enum)은 각 케이스마다 고유한 문자열(string) 또는 정수(int) 값을 부여하여 데이터베이스 저장, API 연동 또는 상호 운용성에 유용합니다. 열거형은 깨지기 쉬운 원시 문자열, 정수 또는 상수를 타입 안전하고 자체 문서화되는 값으로 대체하여 도메인 모델링을 개선합니다. 또한 PHP 열거형은 메서드를 가질 수 있고 인터페이스를 구현할 수 있으며, 모든 열거형은 케이스 목록 조회를 지원하고 백트 열거형은 스칼라 값과의 상호 변환을 지원합니다.

enum OrderState
{
    case Draft;
    case Paid;
    case Shipped;
}

enum PaymentStatus: string
{
    case Pending = 'pending';
    case Complete = 'complete';
    case Failed = 'failed';
}

$status = PaymentStatus::tryFrom('complete');
var_dump($status === PaymentStatus::Complete);
AI 코치와 함께 이 질문에 답해 보세요

미들 질문

8스칼라, 널 허용(nullable), 유니언, 인터섹션, DNF (Disjunctive Normal Form) 타입은 PHP의 API 규약을 어떻게 표현하며, 각각 언제 사용해야 하나요?

PHP의 타입 선언은 API가 허용하거나 반환하는 값을 명시합니다. `int`, `float`, `string`, `bool`과 같은 스칼라 타입은 정확히 하나의 원시 타입 종류만 기대할 때 사용합니다. `?T` 또는 `T|null`로 작성하는 널 허용(nullable) 타입은 `T` 타입의 값 또는 `null`을 허용합니다. `A|B`와 같은 유니언 타입은 여러 대안 중 하나를 허용합니다. `A&B`와 같은 인터섹션 타입은 값이 나열된 모든 타입을 만족해야 하며, 주로 여러 인터페이스를 동시에 충족해야 할 때 사용합니다. DNF 타입은 `(A&B)|C`처럼 유니언과 인터섹션을 결합하여 더 복잡한 계약을 표현합니다. 각각은 불필요하게 넓거나 혼란스러운 타입을 피하고 실제 API 보증(promise)과 일치할 때 사용해야 합니다.

<?php
interface Cacheable {}

class Report implements Cacheable, JsonSerializable {
    public function jsonSerialize(): mixed { return []; }
}

function findName(int $id): ?string {
    return $id > 0 ? 'Alice' : null;
}

function normalize(int|string $id): string {
    return (string) $id;
}

function store(Cacheable&JsonSerializable $item): void {
    // must satisfy both interfaces
}

function handle((Cacheable&JsonSerializable)|string $input): void {
    // either an object satisfying both contracts, or a string
}
AI 코치와 함께 이 질문에 답해 보세요

9PHP에서 callable, Closure, 일급 콜러블(first-class callable), 호출 가능 객체(invokable object), 콜러블 배열(callable array)의 차이점은 무엇인가요?

`callable`은 함수 이름 문자열, `Closure`, 호출 가능 객체, 또는 `[$object, 'method']`나 `[ClassName::class, 'method']`와 같은 메서드 참조 배열 등 PHP가 호출할 수 있는 모든 값을 포괄하는 광범위한 타입이자 규약입니다. `Closure`는 구체적인 익명 함수 객체이며 그 자체로 호출 가능합니다. `$obj->method(...)` 또는 `strlen(...)`과 같은 일급 콜러블 문법은 호출 가능한 표현식으로부터 `Closure` 인스턴스를 생성합니다. 호출 가능 객체는 `__invoke()` 매직 메서드를 구현하고 있어 호출할 수 있는 객체입니다. 콜러블 배열은 메서드 참조를 위해 전통적으로 사용되어 온 2개 요소 배열 형식입니다. 런타임에 호출 가능 여부를 확인하려면 `is_callable()`을 사용할 수 있습니다.

class Greeter {
    public function hello(string $name): string { return "Hello $name"; }
    public function __invoke(string $name): string { return "Hi $name"; }
}

$g = new Greeter();

$closure = fn(string $name) => "Hey $name";
$arrayCallable = [$g, 'hello'];
$firstClass = $g->hello(...);
$invokable = $g;

foreach ([$closure, $arrayCallable, $firstClass, $invokable] as $cb) {
    echo $cb('Sam'), PHP_EOL;
}
AI 코치와 함께 이 질문에 답해 보세요

10PHP는 정수 오버플로, 부동소수점 정밀도 및 정확한 십진수 계산을 어떻게 처리하나요?

PHP의 정수 크기는 플랫폼에 따라 결정되며, `PHP_INT_MAX`와 같은 상수로 한계값이 제공됩니다. PHP는 정수 오버플로 발생 시 원시 정수 연산 결과를 임의 정밀도 정수로 자동 변환하지 않습니다. 오버플로된 정수 연산은 `float` 타입으로 바뀌거나 고유한 정수 의미를 잃게 됩니다. PHP의 부동소수점(`float`)은 이진 부동소수점 값이므로 `0.1`과 같은 많은 십진 소수가 근사치로 표현됩니다. 따라서 정확한 십진수나 금액 계산을 수행할 때는 부동소수점을 피하고, 상황에 맞게 정수 최소 통화 단위(예: 센트, 원)를 사용하거나 BCMath와 같은 고정/임의 정밀도 십진 연산 도구를 사용해야 합니다. 임의 정밀도 정수 연산에는 GMP가 유용합니다.

var_dump(0.1 + 0.2 === 0.3);
printf('%.17f\n', 0.1 + 0.2);
AI 코치와 함께 이 질문에 답해 보세요

11PHP의 참조(&)와 쓰기 시점 복사(copy-on-write), 그리고 참조가 예상치 못한 부작용을 일으키는 방식에 대해 설명해 주세요.

PHP 변수는 내부적으로 zval 구조체로 표현됩니다. 배열이나 문자열 같은 값에 대해 PHP는 쓰기 시점 복사(copy-on-write) 방식을 사용합니다. 즉, `$b = $a`와 같이 할당하면 한 변수가 수정되기 전까지는 내부적으로 동일한 값을 공유하다가, 수정이 발생하는 시점에 PHP가 값을 분리하여 복사합니다. `&`로 생성된 참조는 변수들이 동일한 컨테이너를 가리키는 별칭이 되도록 만듭니다. 따라서 한 변수를 변경하면 다른 변수도 변경되며, 일반적인 할당에서 기대하는 분리 동작을 우회할 수 있습니다. 참조에 의한 전달(pass by reference)을 사용하면 함수가 호출자의 변수를 직접 수정할 수도 있습니다. 참조는 특히 `foreach ($array as &$value)`와 함께 사용할 때 의도치 않은 부작용을 유발할 수 있습니다. 루프가 종료된 후에도 루프 변수가 마지막 요소를 가리키는 참조 상태로 남아있기 때문에, `unset($value)`를 호출하지 않고 나중에 해당 변수를 재사용하면 의도치 않게 배열의 마지막 요소를 덮어쓸 위험이 있습니다.

$a = [1, 2];
$b = $a;      // shared until modification
$b[0] = 99;   // separates; $a is unchanged
var_dump($a, $b);

$c =& $a;     // alias/reference
$c[0] = 42;   // modifies $a too
var_dump($a, $c);
AI 코치와 함께 이 질문에 답해 보세요

12PHP에서 객체 핸들, 대입, clone, __clone, 얕은 복사, 깊은 복사는 각각 어떻게 동작하나요?

PHP에서 객체 변수는 전체 객체 복사본이 아닌 객체에 대한 핸들을 저장합니다. `$b = $a`와 같이 대입하면 핸들이 복사되므로 두 변수가 동일한 객체를 가리키게 되며, `&` 참조 연산자를 사용하지 않더라도 어느 한쪽 변수를 통해 객체를 변경하면 다른 쪽에도 즉시 반영됩니다. `clone $a`는 원본 객체의 프로퍼티를 얕은 복사(shallow copy)하여 새로운 객체 인스턴스를 생성합니다. 만약 프로퍼티 자체가 객체라면, 명시적으로 복제하지 않는 한 복제된 객체의 프로퍼티도 동일한 중첩 객체를 계속 가리킵니다. `__clone()` 메서드는 얕은 복사가 완료된 후 새 객체에서 호출되며, ID 재설정, 리소스 분리, 중첩 객체의 깊은 복사(deep copy) 등을 수행하는 위치로 사용됩니다. 깊은 복사는 공유되어서는 안 되는 중첩된 가변 객체들을 재귀적으로 복사하는 것을 의미합니다.

class Address {
    public function __construct(public string $city) {}
}

class User {
    public function __construct(public string $name, public Address $address) {}

    public function __clone() {
        $this->address = clone $this->address; // make nested object independent
    }
}

$u1 = new User('Ann', new Address('Paris'));
$u2 = $u1;          // same object handle
$u2->name = 'Beth';
echo $u1->name, PHP_EOL;

$u3 = clone $u1;    // new User object; __clone deep-clones address
$u3->address->city = 'Rome';
echo $u1->address->city, PHP_EOL;
echo $u3->address->city, PHP_EOL;
AI 코치와 함께 이 질문에 답해 보세요

시니어 질문

13데이터베이스 연결 고갈 문제를 어떻게 트러블슈팅하고, PHP-FPM(FastCGI Process Manager) 및 장시간 실행되는 워커를 위한 연결 관리 방식을 어떻게 설계하시겠습니까?

연결 고갈을 해결하기 위해 먼저 데이터베이스의 최대 연결 수 및 현재 활성 세션 수를 전체 PHP 동시성 수준과 비교합니다. 활성화된 각 PHP-FPM 자식 프로세스는 자체 DB 연결을 점유할 수 있으며, 애플리케이션 풀, 호스트 수, 크론 작업, 큐 워커, 마이그레이션 및 배포가 늘어날수록 연결 요구량이 곱절로 증가합니다. 이를 점검하기 위해 DB 세션 뷰, 연결 상태, 트랜잭션 유휴(idle-in-transaction) 세션, 슬로우 쿼리, 락 대기, 에러 로그, 연결 풀 지표, 최근 트래픽 및 워커 수의 변화를 조사합니다. PHP-FPM의 경우, max_children과 호스트별 동시성을 데이터베이스 허용 한도 내로 유지하고, 요청 범위 내에서만 짧게 트랜잭션과 연결을 유지하며, 즉시 커밋 또는 롤백하고, 응답이 느린 외부 호출 중에는 연결을 점유하지 않도록 해야 합니다. PDO 영속 연결(persistent connection)은 연결 오버헤드를 줄일 수 있지만, FPM 프로세스마다 많은 유휴 연결을 고정시키고 세션 상태를 유지할 수 있으므로 주의해서 사용해야 합니다. 애플리케이션 호환성이 확보된다면 PgBouncer와 같은 풀러나 프록시를 도입해 다수의 PHP 클라이언트를 적은 수의 DB 서버 연결로 다중화할 수 있습니다. 장시간 실행되는 워커의 경우 연결 수명주기를 명시적으로 관리해야 합니다. 즉, 헬스 체크를 수행하고 유휴 타임아웃, 프로세스 재시작, 장애 조치(failover), fork 이후 재연결을 처리하며, 세션 상태를 초기화하고 안전하거나 멱등성이 보장된 작업만 재시도하도록 구성해야 합니다.

Example:
- 6 application servers
- 40 PHP-FPM max_children per server
- 2 separate FPM pools that can hit the database

Potential concurrent PHP request DB connections = 6 * 40 * 2 = 480

If PostgreSQL max_connections is 300 and queues/cron/admin tools also connect,
connection exhaustion is expected during traffic spikes.
AI 코치와 함께 이 질문에 답해 보세요

14PHP(PHP: Hypertext Preprocessor) 환경에서 읽기 복제본, 읽기/쓰기 분리, 복제 지연, 프라이머리 데이터베이스 장애 조치(failover)를 어떻게 처리하시겠습니까?

PHP에서 읽기 복제본과 장애 조치를 처리하려면 읽기와 쓰기 트래픽을 분리하고, 복제 지연을 완화하며, 장애 조치 발생 시 연결 단절을 안정적으로 관리해야 합니다. 애플리케이션 계층에서는 데이터베이스 설정이나 미들웨어를 통해 읽기 쿼리(`SELECT`)는 읽기 복제본으로, 쓰기 쿼리(`INSERT`, `UPDATE`, `DELETE`)는 프라이머리 인스턴스로 라우팅합니다(또는 ProxySQL, AWS RDS Proxy 같은 데이터베이스 프록시를 통해 관리할 수도 있습니다). 복제는 비동기식으로 이루어지기 때문에 복제 지연으로 인해 과거 데이터가 조회될 위험이 있습니다. '자신이 쓴 데이터 읽기(read-your-writes)' 일관성을 보장하기 위해, 활성 트랜잭션 내부의 읽기 작업은 프라이머리로 라우팅해야 하며, 쓰기 직후 특정 사용자 세션이나 요청 수명 주기 동안 이어지는 후속 읽기 작업도 임시로 프라이머리에 고정해야 합니다. 프라이머리 장애 조치 시 최신 데이터베이스 클러스터는 복제본을 프라이머리로 승격하고 DNS 또는 프록시 엔드포인트를 갱신합니다. PHP에서는 연결 계층에서 연결 유실 및 읽기 전용(read-only) 에러를 감지하여 유효하지 않은 PDO 소켓을 정리하고 지수 백오프(exponential backoff)를 적용한 통제된 재시도를 수행해야 합니다.

<?php
// config/database.php
return [
    'mysql' => [
        'read' => [
            'host' => [env('DB_REPLICA_HOST_1'), env('DB_REPLICA_HOST_2')],
        ],
        'write' => [
            'host' => [env('DB_PRIMARY_HOST')],
        ],
        'sticky'    => true,
        'driver'    => 'mysql',
        'database'  => env('DB_DATABASE'),
        'username'  => env('DB_USERNAME'),
        'password'  => env('DB_PASSWORD'),
        'charset'   => 'utf8mb4',
        'collation' => 'utf8mb4_unicode_ci',
    ],
];
AI 코치와 함께 이 질문에 답해 보세요

15PHP 애플리케이션에서 다운타임을 없애거나 최소화하면서 데이터베이스 마이그레이션과 스키마 변경을 안전하게 배포하려면 어떻게 해야 하나요?

PHP 애플리케이션에서 무중단 데이터베이스 마이그레이션을 배포하려면 넌블로킹(non-blocking) 온라인 DDL(Data Definition Language) 기법과 함께 확장 및 축소(Expand and Contract, 병렬 운영) 패턴을 사용하여 스키마 변경을 애플리케이션 코드 배포와 분리해야 합니다. 확장(Expand) 단계에서는 하위 호환성이 보장되는 스키마 변경 사항을 적용합니다(예: NULL 허용 컬럼 추가, 새 테이블 생성, 또는 pt-online-schema-change, gh-ost, MySQL/PostgreSQL의 네이티브 온라인 DDL 도구를 활용한 동시 인덱스 생성). 그런 다음 애플리케이션을 배포하여 기존 스키마 구조와 새 스키마 구조 모두에 쓰는 듀얼 쓰기(dual-writing)를 지원하면서, 읽기는 둘 중 한곳에서 수행하도록 합니다. 이후 테이블 락이나 복제 지연(replication lag)을 방지하기 위해 스로틀링(속도 제한)이 적용된 백그라운드 배치로 기존 데이터를 비동기 백필합니다. 축소(Contract) 단계에서는 데이터 일관성이 검증되고 읽기 작업이 새 구조로 완전히 전환된 후, 코드 배포를 통해 구버전 스키마에 대한 참조를 제거합니다. 마지막으로 정리 마이그레이션을 수행하여 기존 컬럼이나 테이블을 삭제(drop)합니다. 롤링 배포 도중 안전하게 롤백할 수 있으려면 각각의 중간 스키마 상태가 이전 버전 및 신규 버전 애플리케이션 모두와 완전히 호환되어야 합니다.

<?php
// Step 1: Migration (Expand) -> Add new column as nullable
Schema::table('users', function (Blueprint $table) {
    $table->string('full_name')->nullable()->after('name');
});

// Step 2: Code deployment -> Write to both, read with fallback
class User extends Model {
    public function setNameAttribute($value) {
        $this->attributes['name'] = $value;
        $this->attributes['full_name'] = $value; // Dual write
    }
}

// Step 3: Background worker backfills existing rows in small batches
// Step 4: Code deployment -> Read/write exclusively from 'full_name'
// Step 5: Migration (Contract) -> Drop legacy 'name' column
AI 코치와 함께 이 질문에 답해 보세요