Dokumen ini menjelaskan cara menggunakan konektor Paimon—konektor streaming data lakehouse—bersama Paimon Catalog.
Informasi latar belakang
Apache Paimon adalah format penyimpanan data lake yang menyatukan pemrosesan streaming dan batch. Paimon mendukung penulisan throughput tinggi serta kueri latensi rendah. Mesin komputasi umum pada platform big data open-source Alibaba Cloud E-MapReduce, seperti Flink, Spark, Hive, dan Trino, telah terintegrasi dengan baik bersama Paimon. Anda dapat menggunakan Apache Paimon untuk membangun layanan penyimpanan data lake di Hadoop Distributed File System (HDFS) atau Object Storage Service (OSS), lalu menghubungkannya ke mesin komputasi tersebut guna melakukan analitik data lake. Untuk informasi selengkapnya, lihat Apache Paimon.
Kategori | Detail |
Tipe yang didukung | Tabel sumber, tabel dimensi, tabel sink, dan tujuan ingesti data |
Mode eksekusi | Mode streaming dan batch |
Format data | Tidak didukung |
Metrik pemantauan spesifik | Tidak ada |
Tipe API | SQL, pekerjaan YAML untuk ingesti data |
Mendukung pembaruan atau penghapusan data di tabel sink | Ya |
Fitur
Apache Paimon menyediakan kemampuan inti berikut:
Membangun layanan penyimpanan data lake berbiaya rendah dan ringan berbasis HDFS atau OSS.
Membaca dan menulis dataset berskala besar dalam mode streaming dan batch.
Menjalankan kueri batch dan kueri Online Analytical Processing (OLAP) dengan ketepatan waktu data mulai dari hitungan detik hingga menit.
Mengonsumsi dan menghasilkan data inkremental. Paimon dapat digunakan sebagai lapisan penyimpanan baik untuk gudang data offline tradisional maupun gudang data streaming modern.
Menjalankan pre-agregasi data untuk mengurangi biaya penyimpanan dan tekanan komputasi downstream.
Mengambil versi historis data.
Menyaring data secara efisien.
Mendukung evolusi skema.
Batasan dan rekomendasi
Konektor Paimon hanya didukung pada mesin komputasi Flink Ververica Runtime (VVR) versi 6.0.6 dan yang lebih baru.
Tabel berikut menjelaskan pemetaan versi antara Paimon dan VVR.
Versi Apache Paimon
Versi VVR
1.3
11.4
1.2
11.2、11.3
1.1
11
1.0
8.0.11
0.9
8.0.7, 8.0.8, 8.0.9, dan 8.0.10
0.8
8.0.6
0.7
8.0.5
0.6
8.0.4
0.6
8.0.3
Rekomendasi penyimpanan untuk skenario penulisan konkuren
Saat beberapa pekerjaan melakukan pembaruan konkuren pada tabel Paimon yang sama, penyimpanan OSS standar (oss://) dapat menyebabkan konflik commit langka atau error pekerjaan karena keterbatasan atomisitas operasi file.
Untuk memastikan penulisan yang stabil secara konsisten, kami merekomendasikan penggunaan layanan metadata atau penyimpanan yang menyediakan jaminan atomisitas kuat. Prioritaskan penggunaan Data Lake Formation (DLF), yang menyediakan manajemen terpadu untuk metadata dan layanan penyimpanan Paimon. Alternatif lainnya adalah OSS-HDFS atau HDFS.
SQL
Konektor Paimon dapat digunakan dalam pekerjaan SQL sebagai tabel sumber atau tabel sink.
Sintaks
Jika Anda membuat tabel Paimon di Paimon Catalog, Anda tidak perlu menentukan parameter
connector. Sintaks untuk membuat tabel Paimon adalah sebagai berikut:CREATE TABLE `<YOUR-PAIMON-CATALOG>`.`<YOUR-DB>`.paimon_table ( id BIGINT, data STRING, PRIMARY KEY (id) NOT ENFORCED ) WITH ( ... );CatatanJika tabel Paimon telah dibuat di Paimon Catalog, Anda dapat langsung menggunakannya tanpa perlu membuatnya lagi.
Jika Anda membuat tabel temporary Paimon di katalog lain, Anda harus menentukan parameter connector dan path penyimpanan tabel Paimon. Sintaks untuk membuat tabel Paimon adalah sebagai berikut:
CREATE TEMPORARY TABLE paimon_table ( id BIGINT, data STRING, PRIMARY KEY (id) NOT ENFORCED ) WITH ( 'connector' = 'paimon', 'path' = '<path-to-paimon-table-files>', 'auto-create' = 'true', -- Jika tidak ada file data tabel Paimon di path yang ditentukan, file akan dibuat secara otomatis. ... );CatatanContoh path:
'path' = 'oss://<bucket>/test/order.db/orders'. Jangan menghilangkan akhiran.db. Paimon menggunakan konvensi penamaan ini untuk mengidentifikasi database.Beberapa pekerjaan yang menulis ke tabel yang sama harus menggunakan konfigurasi path yang identik.
Jika dua nilai path berbeda, Paimon menganggapnya sebagai tabel yang berbeda. Bahkan jika path fisiknya sama, konfigurasi Catalog yang tidak konsisten dapat menyebabkan konflik penulisan konkuren, kegagalan kompaksi, dan kehilangan data. Misalnya,
oss://b/testdanoss://b/test/dianggap sebagai path berbeda karena adanya garis miring di akhir, meskipun path fisiknya sama.
WITH parameters
Parameter | Deskripsi | Tipe data | Wajib | Nilai default | Catatan |
connector | Tipe tabel. | String | Tidak | Tidak ada |
|
path | Path penyimpanan tabel. | String | Tidak | Tidak ada |
|
auto-create | Saat membuat tabel temporary Paimon, tentukan apakah file harus dibuat secara otomatis jika tidak ada file tabel Paimon di path yang ditentukan. | Boolean | Tidak | false | Nilai yang valid:
|
bucket | Jumlah bucket di setiap partisi. | Integer | Tidak | 1 | Data yang ditulis ke tabel Paimon didistribusikan ke setiap bucket berdasarkan Catatan Kami merekomendasikan agar volume data setiap bucket kurang dari 5 GB. |
bucket-key | Kolom kunci untuk bucketing. | String | Tidak | Tidak ada | Menentukan kolom yang digunakan untuk mendistribusikan data yang ditulis ke tabel Paimon ke bucket yang berbeda. Pisahkan nama kolom dengan koma (,), misalnya, Catatan
|
changelog-producer | Mekanisme untuk menghasilkan data inkremental. | String | Tidak | none | Paimon dapat menghasilkan data inkremental lengkap (semua data update_after memiliki data update_before yang sesuai) untuk aliran data masukan apa pun, yang memudahkan konsumen downstream. Nilai yang valid untuk mekanisme pembangkitan data inkremental:
Untuk informasi selengkapnya tentang cara memilih mekanisme pembangkitan data inkremental, lihat Mekanisme pembangkitan data inkremental. |
full-compaction.delta-commits | Interval maksimum untuk full compaction. | Integer | Tidak | Tidak ada | Parameter ini menentukan jumlah commit snapshot setelah mana full compaction harus dilakukan. |
lookup.cache-max-memory-size | Ukuran cache memori untuk tabel dimensi Paimon. | String | Tidak | 256 MB | Parameter ini memengaruhi ukuran cache baik untuk tabel dimensi maupun lookup changelog-producer. Ukuran cache untuk kedua mekanisme dikonfigurasi oleh parameter ini. |
merge-engine | Mekanisme untuk menggabungkan data dengan kunci primer yang sama. | String | Tidak | deduplicate | Nilai yang valid:
Untuk analisis mendetail tentang mekanisme penggabungan data, lihat Mekanisme penggabungan data. |
partial-update.ignore-delete | Menentukan apakah pesan hapus (-D) diabaikan. | Boolean | Tidak | false | Nilai yang valid:
Catatan
|
ignore-delete | Menentukan apakah pesan hapus (-D) diabaikan. | Boolean | Tidak | false | Nilai yang valid sama dengan untuk partial-update.ignore-delete. Catatan
|
partition.default-name | Nama default untuk partisi. | String | Tidak | __DEFAULT_PARTITION__ | Jika nilai kolom kunci partisi adalah null atau string kosong, nama default ini digunakan sebagai nama partisi. |
partition.expiration-check-interval | Interval pemeriksaan partisi yang kedaluwarsa. | String | Tidak | 1h | Untuk informasi selengkapnya, lihat Bagaimana cara mengonfigurasi kedaluwarsa partisi otomatis? |
partition.expiration-time | Durasi kedaluwarsa untuk partisi. | String | Tidak | Tidak ada | Saat waktu kelangsungan hidup partisi melebihi nilai ini, partisi tersebut kedaluwarsa. Secara default, partisi tidak pernah kedaluwarsa. Waktu kelangsungan hidup partisi dihitung dari nilai partisinya. Untuk informasi selengkapnya, lihat Bagaimana cara mengonfigurasi kedaluwarsa partisi otomatis? |
partition.timestamp-formatter | String format untuk mengonversi string waktu menjadi Stempel waktu UNIX. | String | Tidak | Tidak ada | Menetapkan format untuk mengekstraksi waktu kelangsungan hidup partisi dari nilai partisi. Untuk informasi selengkapnya, lihat Bagaimana cara mengonfigurasi kedaluwarsa partisi otomatis? |
partition.timestamp-pattern | String format untuk mengonversi nilai partisi menjadi string waktu. | String | Tidak | Tidak ada | Menetapkan format untuk mengekstraksi waktu kelangsungan hidup partisi dari nilai partisi. Untuk informasi selengkapnya, lihat Bagaimana cara mengonfigurasi kedaluwarsa partisi otomatis? |
scan.bounded.watermark | Saat watermark data yang dihasilkan oleh tabel sumber Paimon melebihi nilai ini, tabel sumber Paimon berhenti menghasilkan data. | Long | Tidak | Tidak ada | Tidak ada. |
scan.mode | Menentukan offset konsumen untuk tabel sumber Paimon. | String | Tidak | default | Untuk informasi selengkapnya, lihat Bagaimana cara menyetel offset konsumen untuk tabel sumber Paimon? |
scan.snapshot-id | Menentukan snapshot tempat tabel sumber Paimon mulai mengonsumsi. | Integer | Tidak | Tidak ada | Untuk informasi selengkapnya, lihat Bagaimana cara menyetel offset konsumen untuk tabel sumber Paimon? |
scan.timestamp-millis | Menentukan titik waktu tempat tabel sumber Paimon mulai mengonsumsi. | Integer | Tidak | Tidak ada | Untuk informasi selengkapnya, lihat Bagaimana cara menyetel offset konsumen untuk tabel sumber Paimon? |
snapshot.num-retained.max | Jumlah maksimum snapshot terbaru yang dipertahankan tanpa kedaluwarsa. | Integer | Tidak | 2147483647 | Snapshot kedaluwarsa jika konfigurasi ini atau `snapshot.time-retained` terpenuhi, dan `snapshot.num-retained.min` juga terpenuhi. |
snapshot.num-retained.min | Jumlah minimum snapshot terbaru yang dipertahankan tanpa kedaluwarsa. | Integer | Tidak | 10 | Tidak ada. |
snapshot.time-retained | Durasi hingga snapshot kedaluwarsa. | String | Tidak | 1h | Snapshot kedaluwarsa jika konfigurasi ini atau `snapshot.num-retained.max` terpenuhi, dan `snapshot.num-retained.min` juga terpenuhi. |
write-mode | Mode penulisan untuk tabel Paimon. | String | Tidak | change-log | Nilai yang valid:
Untuk informasi selengkapnya tentang mode penulisan, lihat Mode penulisan. |
scan.infer-parallelism | Menentukan apakah konkurensi tabel sumber Paimon diinferensi secara otomatis. | Boolean | Tidak | true | Nilai yang valid:
|
scan.parallelism | Konkurensi tabel sumber Paimon. | Integer | Tidak | Tidak ada | Catatan Pada tab , parameter ini tidak berlaku saat Resource Mode diatur ke Expert. |
sink.parallelism | Konkurensi tabel sink Paimon. | Integer | Tidak | Tidak ada | Catatan Pada tab , parameter ini tidak berlaku saat Resource Mode diatur ke Expert. |
sink.clustering.by-columns | Menentukan kolom clustering untuk penulisan ke tabel sink Paimon. | String | Tidak | Tidak ada | Untuk tabel append-only Paimon (tabel non-kunci primer), mengonfigurasi parameter ini dalam pekerjaan batch mengaktifkan fitur penulisan clustering. Fitur ini mengelompokkan data pada kolom tertentu berdasarkan rentang nilai, yang meningkatkan kecepatan kueri tabel. Pisahkan beberapa nama kolom dengan koma (,), misalnya, Untuk informasi selengkapnya tentang clustering, lihat dokumentasi resmi Apache Paimon. |
sink.delete-strategy | Menetapkan kebijakan validasi untuk memastikan sistem menangani pesan retraction (-D/-U) dengan benar. | Enum | Tidak | NONE | Nilai valid untuk kebijakan validasi dan perilaku yang diharapkan dari operator sink dalam menangani pesan retraction adalah sebagai berikut:
Catatan
|
Untuk informasi selengkapnya tentang item konfigurasi, lihat dokumentasi resmi Apache Paimon.
Detail fitur
Jaminan ketepatan waktu dan konsistensi data
Tabel sink Paimon menggunakan protokol two-phase commit untuk mengomit data yang ditulis selama setiap checkpoint pekerjaan Flink. Oleh karena itu, ketepatan waktu data sama dengan interval checkpoint pekerjaan Flink. Setiap commit menghasilkan hingga dua snapshot.
Saat dua pekerjaan Flink menulis ke tabel Paimon yang sama secara bersamaan, jika data dari kedua pekerjaan tersebut tidak ditulis ke bucket yang sama, konsistensi serializable dijamin. Jika data dari kedua pekerjaan tersebut ditulis ke bucket yang sama, hanya konsistensi snapshot isolation yang dijamin. Artinya, data dalam tabel mungkin merupakan campuran hasil dari kedua pekerjaan tersebut, tetapi tidak ada data yang hilang.
Mekanisme penggabungan data
Saat tabel sink Paimon menerima beberapa catatan dengan kunci primer yang sama, tabel tersebut menggabungkan catatan-catatan tersebut menjadi satu catatan untuk menjaga keunikan kunci primer. Anda dapat menentukan perilaku penggabungan data dengan mengatur parameter merge-engine. Tabel berikut menjelaskan mekanisme penggabungan data.
Mekanisme penggabungan | Detail |
Deduplicate | Mekanisme deduplicate adalah mekanisme penggabungan data default. Untuk beberapa catatan dengan kunci primer yang sama, tabel sink Paimon hanya menyimpan catatan terbaru dan membuang yang lainnya. Catatan Jika catatan terbaru adalah pesan hapus, semua catatan dengan kunci primer tersebut dibuang. |
Partial Update | Dengan menentukan mekanisme partial-update, Anda dapat memperbarui data secara progresif melalui beberapa pesan hingga akhirnya mendapatkan data lengkap. Secara spesifik, data baru dengan kunci primer yang sama akan menimpa data lama, tetapi kolom dengan nilai null tidak akan ditimpa. Misalnya, asumsikan tabel sink Paimon menerima tiga catatan berikut secara berurutan:
Kolom pertama adalah kunci primer. Hasil akhirnya adalah <1, 25.2, 10, 'This is a book'>. Catatan
|
Aggregation | Dalam beberapa skenario, Anda mungkin hanya peduli pada nilai agregat. Mekanisme aggregation mengagregasi data dengan kunci primer yang sama berdasarkan fungsi agregat yang Anda tentukan. Untuk setiap kolom yang bukan bagian dari kunci primer, Anda harus menentukan fungsi agregat menggunakan Kolom price diagregasi menggunakan fungsi max, dan kolom sales diagregasi menggunakan fungsi sum. Diberikan dua catatan masukan <1, 23.0, 15> dan <1, 30.2, 20>, hasil akhirnya adalah <1, 30.2, 35>. Fungsi agregat yang didukung saat ini beserta tipe datanya adalah sebagai berikut:
Catatan
|
Mekanisme pembangkitan data inkremental
Dengan mengatur parameter changelog-producer ke mekanisme pembangkitan data inkremental yang sesuai, Paimon dapat menghasilkan data inkremental lengkap untuk aliran data masukan apa pun. Data inkremental lengkap berarti bahwa semua data update_after memiliki data update_before yang sesuai. Berikut adalah daftar semua mekanisme pembangkitan data inkremental. Untuk informasi selengkapnya, lihat dokumentasi resmi Apache Paimon.
Mekanisme | Detail |
None | Jika Anda mengatur Misalnya, asumsikan konsumen downstream perlu menghitung jumlah suatu kolom. Jika konsumen hanya melihat data terbaru, yaitu 5, konsumen tidak dapat menentukan cara memperbarui jumlah tersebut. Jika data sebelumnya adalah 4, jumlah harus ditambah 1. Jika data sebelumnya adalah 6, jumlah harus dikurangi 1. Konsumen semacam ini sensitif terhadap data update_before. Kami merekomendasikan agar Anda tidak mengatur mekanisme pembangkitan data inkremental ke `none`. Namun, mekanisme lain akan menimbulkan overhead performa. Catatan Jika konsumen downstream Anda, seperti database, tidak sensitif terhadap data update_before, Anda dapat mengatur mekanisme pembangkitan data inkremental ke `none`. Oleh karena itu, kami merekomendasikan agar Anda mengonfigurasi mekanisme berdasarkan kebutuhan aktual Anda. |
Input | Jika Anda mengatur Oleh karena itu, mekanisme ini hanya dapat digunakan ketika aliran data masukan itu sendiri merupakan data inkremental lengkap, seperti data Change Data Capture (CDC). |
Lookup | Jika Anda mengatur Dibandingkan dengan mekanisme Full Compaction yang dijelaskan di bawah, mekanisme Lookup memberikan ketepatan waktu yang lebih baik dalam menghasilkan data inkremental tetapi mengonsumsi lebih banyak sumber daya secara keseluruhan. Kami merekomendasikan agar Anda menggunakan mekanisme ini ketika Anda memiliki persyaratan tinggi terhadap ketepatan waktu data, seperti ketepatan waktu tingkat menit. |
Full Compaction | Jika Anda mengatur Dibandingkan dengan mekanisme Lookup, mekanisme Full Compaction memiliki ketepatan waktu yang lebih buruk dalam menghasilkan data inkremental. Namun, mekanisme ini memanfaatkan proses full compaction data dan tidak menghasilkan komputasi tambahan, sehingga mengonsumsi lebih sedikit sumber daya secara keseluruhan. Kami merekomendasikan agar Anda menggunakan mekanisme ini ketika Anda tidak memiliki persyaratan tinggi terhadap ketepatan waktu data, seperti ketepatan waktu tingkat jam. |
Mode penulisan
Tabel Paimon saat ini mendukung mode penulisan berikut.
Mode | Detail |
Change-log | Mode penulisan change-log adalah mode penulisan default untuk tabel Paimon. Mode ini mendukung penyisipan, penghapusan, dan pembaruan data berdasarkan kunci primer. Anda juga dapat menggunakan mekanisme penggabungan data dan pembangkitan data inkremental yang disebutkan di atas dalam mode ini. |
Append-only | Mode penulisan append-only hanya mendukung penyisipan data dan tidak mendukung kunci primer. Mode ini lebih efisien daripada mode change-log dan dapat digunakan sebagai alternatif antrian pesan dalam skenario di mana persyaratan ketepatan waktu data tidak tinggi, seperti ketepatan waktu tingkat menit. Untuk informasi selengkapnya tentang mode penulisan append-only, lihat dokumentasi resmi Apache Paimon. Saat menggunakan mode penulisan append-only, perhatikan hal berikut:
|
Sebagai tujuan untuk CTAS dan CDAS
Tabel Paimon mendukung sinkronisasi data real-time pada tingkat tabel tunggal atau seluruh database. Jika skema tabel upstream berubah selama sinkronisasi, perubahan tersebut juga disinkronkan ke tabel Paimon secara real-time. Untuk informasi selengkapnya, lihat Kelola tabel Paimon dan Kelola Katalog Paimon.
Ingesti Data
Konektor Paimon dapat digunakan dalam pengembangan pekerjaan YAML untuk ingesti data sebagai sink.
Sintaks
sink:
type: paimon
name: Paimon Sink
catalog.properties.metastore: filesystem
catalog.properties.warehouse: /path/warehouseItem konfigurasi
Parameter | Deskripsi | Wajib | Tipe data | Nilai default | Catatan |
type | Tipe konektor. | Ya | STRING | Tidak ada | Bidang statis diatur ke |
name | Nama sink. | Tidak | STRING | Tidak ada | Nama sink. |
catalog.properties.metastore | Tipe Paimon Catalog. | Tidak | STRING | filesystem | Nilai yang valid:
|
catalog.properties.* | Parameter untuk membuat Paimon Catalog. | Tidak | STRING | Tidak ada | Untuk informasi selengkapnya, lihat Kelola Katalog Paimon. |
table.properties.* | Parameter untuk membuat tabel Paimon. | Tidak | STRING | Tidak ada | Untuk informasi selengkapnya, lihat Opsi tabel Paimon. |
catalog.properties.warehouse | Direktori root untuk penyimpanan file. | Tidak | STRING | Tidak ada | Parameter ini hanya berlaku saat |
commit.user-prefix | Awalan nama pengguna untuk melakukan commit file data. | Tidak | STRING | Tidak ada | Catatan Kami merekomendasikan agar Anda menetapkan username berbeda untuk pekerjaan berbeda agar mudah melokalisasi pekerjaan yang bertentangan saat terjadi konflik commit. |
partition.key | Bidang partisi untuk setiap tabel terpartisi. | Tidak | STRING | Tidak ada | Gunakan titik koma ( |
sink.cross-partition-upsert.tables | Menentukan tabel yang memerlukan pembaruan lintas partisi, di mana kunci primer tidak mencakup semua bidang partisi. | Tidak | STRING | Tidak ada | Tabel yang diperbarui lintas partisi.
Penting
|
Contoh
Contoh berikut menunjukkan cara mengonfigurasi Paimon sebagai sink ingesti data berdasarkan tipe Paimon Catalog.
Contoh konfigurasi untuk menulis ke Alibaba Cloud OSS saat Paimon Catalog adalah filesystem:
source: type: mysql name: MySQL Source hostname: ${secret_values.mysql.hostname} port: ${mysql.port} username: ${secret_values.mysql.username} password: ${secret_values.mysql.password} tables: ${mysql.source.table} server-id: 8601-8604 sink: type: paimon name: Paimon Sink catalog.properties.metastore: filesystem catalog.properties.warehouse: oss://default/test catalog.properties.fs.oss.endpoint: oss-cn-beijing-internal.aliyuncs.com catalog.properties.fs.oss.accessKeyId: xxxxxxxx catalog.properties.fs.oss.accessKeySecret: xxxxxxxxUntuk informasi selengkapnya tentang parameter yang diawali dengan catalog.properties, lihat Buat Katalog Filesystem Paimon.
Contoh konfigurasi untuk menulis ke Alibaba Cloud Data Lake Formation saat Paimon Catalog adalah rest:
source: type: mysql name: MySQL Source hostname: ${secret_values.mysql.hostname} port: ${mysql.port} username: ${secret_values.mysql.username} password: ${secret_values.mysql.password} tables: ${mysql.source.table} server-id: 8601-8604 sink: type: paimon name: Paimon Sink catalog.properties.metastore: rest catalog.properties.uri: dlf_uri catalog.properties.warehouse: your_warehouse catalog.properties.token.provider: dlf # (Opsional) Aktifkan deletion vectors untuk meningkatkan performa baca. table.properties.deletion-vectors.enabled: trueUntuk informasi selengkapnya tentang parameter yang diawali dengan catalog.properties, lihat Parameter konfigurasi Katalog Flink CDC.
Evolusi skema
Saat ini, Paimon sebagai sink ingesti data mendukung event evolusi skema berikut:
CREATE TABLE EVENT
ADD COLUMN EVENT
ALTER COLUMN TYPE EVENT (Memodifikasi tipe kolom kunci primer tidak didukung)
RENAME COLUMN EVENT
DROP COLUMN EVENT
TRUNCATE TABLE EVENT
DROP TABLE EVENT
Jika tabel Paimon downstream sudah ada, skema yang ada digunakan untuk penulisan. Sistem tidak mencoba membuat tabel tersebut lagi.
FAQ
Bagaimana cara menyetel offset konsumen untuk tabel sumber Paimon?
Mengapa terjadi error "Heartbeat of TaskManager timed out" saat saya menulis data ke tabel Paimon?
Kesalahan saat menulis pekerjaan Paimon: Materializer sink tidak boleh digunakan bersama sink Paimon
Apakah visibilitas data yang ditulis oleh konektor Paimon terkait dengan interval checkpoint?